react-native/React/Base/RCTEventDispatcher.m

177 lines
4.5 KiB
Mathematica
Raw Normal View History

/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTEventDispatcher.h"
#import "RCTAssert.h"
#import "RCTBridge.h"
better event emitting Summary:Previously, (mostly touch and scroll) event handling on iOS worked in a hybrid way: * All incoming coalesce-able events would be pooled and retrieved by js thread in the beginning of its frame (all of this happens on js thread) * Any non-coalesce-able event would be immediately dispatched on a js thread (triggered from main thread), and if there would be pooled coalesce-able events they would be immediately dispatched at first too. This behavior has a subtle race condition, where two events are produced (on MT) in one order and received in js in different order. See https://github.com/facebook/react-native/issues/5246#issuecomment-198326673 for further explanation of this case. The new event handling is (afaik) what Android already does. When an event comes we add it into a pool of events and dispatch a block on js thread to inform js there are events to be processed. We keep track of whether we did so, so there is at most one of these blocks waiting to be processed. When the block is executed js will process all events that are in pool at that time (NOT at time of enqueuing the block). This creates a single way of processing events and makes it impossible to process them in different order in js. The tricky part was making sure we don't coalesce events across gestures/different scrolls. Before this was achieved by knowing that gestures and scrolls start/end with non-coalesce-able event, so the pool never contained events that shouldn't be coalesced together. That "assumption" doesn't hold now. I've re-added `coalescingKey` and made touch and scroll events use it to prevent coalescing events of the same type that should remain separate in previous diffs (see dependencies). On top of it it decreases latency in events processing in case where we get only coalesce-able events. Previously these would be processed at begging of the next js frame, even when js would be free and could process them sooner. This delay is done, since they would get processed as soon as the enqueued block would run. To illustrate this improvement let's look at these two systraces. Before: https://cloud.githubusercontent.com/assets/713625/14021417/47b35b7a-f1d3-11e5-93dd-4363edfa1923.png After: https://cloud.githubusercontent.com/assets/713625/14021415/4798582a-f1d3-11e5-8715-425596e0781c.png Reviewed By: javache Differential Revision: D3092867 fb-gh-sync-id: 29071780f00fcddb0b1886a46caabdb3da1d5d84 fbshipit-source-id: 29071780f00fcddb0b1886a46caabdb3da1d5d84
2016-04-01 13:53:12 +00:00
#import "RCTBridge+Private.h"
#import "RCTUtils.h"
better event emitting Summary:Previously, (mostly touch and scroll) event handling on iOS worked in a hybrid way: * All incoming coalesce-able events would be pooled and retrieved by js thread in the beginning of its frame (all of this happens on js thread) * Any non-coalesce-able event would be immediately dispatched on a js thread (triggered from main thread), and if there would be pooled coalesce-able events they would be immediately dispatched at first too. This behavior has a subtle race condition, where two events are produced (on MT) in one order and received in js in different order. See https://github.com/facebook/react-native/issues/5246#issuecomment-198326673 for further explanation of this case. The new event handling is (afaik) what Android already does. When an event comes we add it into a pool of events and dispatch a block on js thread to inform js there are events to be processed. We keep track of whether we did so, so there is at most one of these blocks waiting to be processed. When the block is executed js will process all events that are in pool at that time (NOT at time of enqueuing the block). This creates a single way of processing events and makes it impossible to process them in different order in js. The tricky part was making sure we don't coalesce events across gestures/different scrolls. Before this was achieved by knowing that gestures and scrolls start/end with non-coalesce-able event, so the pool never contained events that shouldn't be coalesced together. That "assumption" doesn't hold now. I've re-added `coalescingKey` and made touch and scroll events use it to prevent coalescing events of the same type that should remain separate in previous diffs (see dependencies). On top of it it decreases latency in events processing in case where we get only coalesce-able events. Previously these would be processed at begging of the next js frame, even when js would be free and could process them sooner. This delay is done, since they would get processed as soon as the enqueued block would run. To illustrate this improvement let's look at these two systraces. Before: https://cloud.githubusercontent.com/assets/713625/14021417/47b35b7a-f1d3-11e5-93dd-4363edfa1923.png After: https://cloud.githubusercontent.com/assets/713625/14021415/4798582a-f1d3-11e5-8715-425596e0781c.png Reviewed By: javache Differential Revision: D3092867 fb-gh-sync-id: 29071780f00fcddb0b1886a46caabdb3da1d5d84 fbshipit-source-id: 29071780f00fcddb0b1886a46caabdb3da1d5d84
2016-04-01 13:53:12 +00:00
#import "RCTProfile.h"
[ReactNative] TextInput bug fixes and features Summary: This introduces event counts to make sure JS doesn't set out of date values on native text inputs, which can cause dropped characters and can mess with autocomplete, and obviates the need for the input buffering which added lag and complexity to the component. Made sure to test simulated super-slow JS text event processing to make sure characters aren't dropped, as well as typing obviously correctable words and making sure autocomplete works as expected. TextInput is now a controlled input by default without causing any issues for most cases, so I removed the `controlled` prop. Fixes selection state jumping by restoring it after setting new text values, so highlighting the middle of some text in the new ReWrite example and hitting space will replace that selection with an underscore and keep the cursor at a sensible position as expected, instead of jumping to the end. Ads `maxLength` prop to support the most commonly needed syncronous behavior: preventing the user from typing too many characters. It can also be used to prevent users from continuing to type after entering special characters by changing it to the current length after a regex match. Made sure to verify it works well with pasted input (including in the middle of existing text), truncating it and collapsing the selection the same way it does on the web. Fixes bug in TextEventsExample where it wouldn't show the submit and end events, even though there were firing correctly.
2015-07-21 19:37:24 +00:00
const NSInteger RCTTextUpdateLagWarningThreshold = 3;
NSString *RCTNormalizeInputEventName(NSString *eventName)
{
if ([eventName hasPrefix:@"on"]) {
eventName = [eventName stringByReplacingCharactersInRange:(NSRange){0, 2} withString:@"top"];
} else if (![eventName hasPrefix:@"top"]) {
eventName = [[@"top" stringByAppendingString:[eventName substringToIndex:1].uppercaseString]
stringByAppendingString:[eventName substringFromIndex:1]];
}
return eventName;
}
static NSNumber *RCTGetEventID(id<RCTEvent> event)
{
return @(
event.viewTag.intValue |
(((uint64_t)event.eventName.hash & 0xFFFF) << 32) |
(((uint64_t)event.coalescingKey) << 48)
);
}
@implementation RCTEventDispatcher
{
better event emitting Summary:Previously, (mostly touch and scroll) event handling on iOS worked in a hybrid way: * All incoming coalesce-able events would be pooled and retrieved by js thread in the beginning of its frame (all of this happens on js thread) * Any non-coalesce-able event would be immediately dispatched on a js thread (triggered from main thread), and if there would be pooled coalesce-able events they would be immediately dispatched at first too. This behavior has a subtle race condition, where two events are produced (on MT) in one order and received in js in different order. See https://github.com/facebook/react-native/issues/5246#issuecomment-198326673 for further explanation of this case. The new event handling is (afaik) what Android already does. When an event comes we add it into a pool of events and dispatch a block on js thread to inform js there are events to be processed. We keep track of whether we did so, so there is at most one of these blocks waiting to be processed. When the block is executed js will process all events that are in pool at that time (NOT at time of enqueuing the block). This creates a single way of processing events and makes it impossible to process them in different order in js. The tricky part was making sure we don't coalesce events across gestures/different scrolls. Before this was achieved by knowing that gestures and scrolls start/end with non-coalesce-able event, so the pool never contained events that shouldn't be coalesced together. That "assumption" doesn't hold now. I've re-added `coalescingKey` and made touch and scroll events use it to prevent coalescing events of the same type that should remain separate in previous diffs (see dependencies). On top of it it decreases latency in events processing in case where we get only coalesce-able events. Previously these would be processed at begging of the next js frame, even when js would be free and could process them sooner. This delay is done, since they would get processed as soon as the enqueued block would run. To illustrate this improvement let's look at these two systraces. Before: https://cloud.githubusercontent.com/assets/713625/14021417/47b35b7a-f1d3-11e5-93dd-4363edfa1923.png After: https://cloud.githubusercontent.com/assets/713625/14021415/4798582a-f1d3-11e5-8715-425596e0781c.png Reviewed By: javache Differential Revision: D3092867 fb-gh-sync-id: 29071780f00fcddb0b1886a46caabdb3da1d5d84 fbshipit-source-id: 29071780f00fcddb0b1886a46caabdb3da1d5d84
2016-04-01 13:53:12 +00:00
// 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;
better event emitting Summary:Previously, (mostly touch and scroll) event handling on iOS worked in a hybrid way: * All incoming coalesce-able events would be pooled and retrieved by js thread in the beginning of its frame (all of this happens on js thread) * Any non-coalesce-able event would be immediately dispatched on a js thread (triggered from main thread), and if there would be pooled coalesce-able events they would be immediately dispatched at first too. This behavior has a subtle race condition, where two events are produced (on MT) in one order and received in js in different order. See https://github.com/facebook/react-native/issues/5246#issuecomment-198326673 for further explanation of this case. The new event handling is (afaik) what Android already does. When an event comes we add it into a pool of events and dispatch a block on js thread to inform js there are events to be processed. We keep track of whether we did so, so there is at most one of these blocks waiting to be processed. When the block is executed js will process all events that are in pool at that time (NOT at time of enqueuing the block). This creates a single way of processing events and makes it impossible to process them in different order in js. The tricky part was making sure we don't coalesce events across gestures/different scrolls. Before this was achieved by knowing that gestures and scrolls start/end with non-coalesce-able event, so the pool never contained events that shouldn't be coalesced together. That "assumption" doesn't hold now. I've re-added `coalescingKey` and made touch and scroll events use it to prevent coalescing events of the same type that should remain separate in previous diffs (see dependencies). On top of it it decreases latency in events processing in case where we get only coalesce-able events. Previously these would be processed at begging of the next js frame, even when js would be free and could process them sooner. This delay is done, since they would get processed as soon as the enqueued block would run. To illustrate this improvement let's look at these two systraces. Before: https://cloud.githubusercontent.com/assets/713625/14021417/47b35b7a-f1d3-11e5-93dd-4363edfa1923.png After: https://cloud.githubusercontent.com/assets/713625/14021415/4798582a-f1d3-11e5-8715-425596e0781c.png Reviewed By: javache Differential Revision: D3092867 fb-gh-sync-id: 29071780f00fcddb0b1886a46caabdb3da1d5d84 fbshipit-source-id: 29071780f00fcddb0b1886a46caabdb3da1d5d84
2016-04-01 13:53:12 +00:00
NSMutableDictionary *_eventQueue;
BOOL _eventsDispatchScheduled;
}
@synthesize bridge = _bridge;
RCT_EXPORT_MODULE()
Refactored module access to allow for lazy loading Summary: public The `bridge.modules` dictionary provides access to all native modules, but this API requires that every module is initialized in advance so that any module can be accessed. This diff introduces a better API that will allow modules to be initialized lazily as they are needed, and deprecates `bridge.modules` (modules that use it will still work, but should be rewritten to use `bridge.moduleClasses` or `-[bridge moduleForName/Class:` instead. The rules are now as follows: * Any module that overrides `init` or `setBridge:` will be initialized on the main thread when the bridge is created * Any module that implements `constantsToExport:` will be initialized later when the config is exported (the module itself will be initialized on a background queue, but `constantsToExport:` will still be called on the main thread. * All other modules will be initialized lazily when a method is first called on them. These rules may seem slightly arcane, but they have the advantage of not violating any assumptions that may have been made by existing code - any module written under the original assumption that it would be initialized synchronously on the main thread when the bridge is created should still function exactly the same, but modules that avoid overriding `init` or `setBridge:` will now be loaded lazily. I've rewritten most of the standard modules to take advantage of this new lazy loading, with the following results: Out of the 65 modules included in UIExplorer: * 16 are initialized on the main thread when the bridge is created * A further 8 are initialized when the config is exported to JS * The remaining 41 will be initialized lazily on-demand Reviewed By: jspahrsummers Differential Revision: D2677695 fb-gh-sync-id: 507ae7e9fd6b563e89292c7371767c978e928f33
2015-11-25 11:09:00 +00:00
- (void)setBridge:(RCTBridge *)bridge
{
Refactored module access to allow for lazy loading Summary: public The `bridge.modules` dictionary provides access to all native modules, but this API requires that every module is initialized in advance so that any module can be accessed. This diff introduces a better API that will allow modules to be initialized lazily as they are needed, and deprecates `bridge.modules` (modules that use it will still work, but should be rewritten to use `bridge.moduleClasses` or `-[bridge moduleForName/Class:` instead. The rules are now as follows: * Any module that overrides `init` or `setBridge:` will be initialized on the main thread when the bridge is created * Any module that implements `constantsToExport:` will be initialized later when the config is exported (the module itself will be initialized on a background queue, but `constantsToExport:` will still be called on the main thread. * All other modules will be initialized lazily when a method is first called on them. These rules may seem slightly arcane, but they have the advantage of not violating any assumptions that may have been made by existing code - any module written under the original assumption that it would be initialized synchronously on the main thread when the bridge is created should still function exactly the same, but modules that avoid overriding `init` or `setBridge:` will now be loaded lazily. I've rewritten most of the standard modules to take advantage of this new lazy loading, with the following results: Out of the 65 modules included in UIExplorer: * 16 are initialized on the main thread when the bridge is created * A further 8 are initialized when the config is exported to JS * The remaining 41 will be initialized lazily on-demand Reviewed By: jspahrsummers Differential Revision: D2677695 fb-gh-sync-id: 507ae7e9fd6b563e89292c7371767c978e928f33
2015-11-25 11:09:00 +00:00
_bridge = bridge;
_eventQueue = [NSMutableDictionary new];
_eventQueueLock = [NSLock new];
better event emitting Summary:Previously, (mostly touch and scroll) event handling on iOS worked in a hybrid way: * All incoming coalesce-able events would be pooled and retrieved by js thread in the beginning of its frame (all of this happens on js thread) * Any non-coalesce-able event would be immediately dispatched on a js thread (triggered from main thread), and if there would be pooled coalesce-able events they would be immediately dispatched at first too. This behavior has a subtle race condition, where two events are produced (on MT) in one order and received in js in different order. See https://github.com/facebook/react-native/issues/5246#issuecomment-198326673 for further explanation of this case. The new event handling is (afaik) what Android already does. When an event comes we add it into a pool of events and dispatch a block on js thread to inform js there are events to be processed. We keep track of whether we did so, so there is at most one of these blocks waiting to be processed. When the block is executed js will process all events that are in pool at that time (NOT at time of enqueuing the block). This creates a single way of processing events and makes it impossible to process them in different order in js. The tricky part was making sure we don't coalesce events across gestures/different scrolls. Before this was achieved by knowing that gestures and scrolls start/end with non-coalesce-able event, so the pool never contained events that shouldn't be coalesced together. That "assumption" doesn't hold now. I've re-added `coalescingKey` and made touch and scroll events use it to prevent coalescing events of the same type that should remain separate in previous diffs (see dependencies). On top of it it decreases latency in events processing in case where we get only coalesce-able events. Previously these would be processed at begging of the next js frame, even when js would be free and could process them sooner. This delay is done, since they would get processed as soon as the enqueued block would run. To illustrate this improvement let's look at these two systraces. Before: https://cloud.githubusercontent.com/assets/713625/14021417/47b35b7a-f1d3-11e5-93dd-4363edfa1923.png After: https://cloud.githubusercontent.com/assets/713625/14021415/4798582a-f1d3-11e5-8715-425596e0781c.png Reviewed By: javache Differential Revision: D3092867 fb-gh-sync-id: 29071780f00fcddb0b1886a46caabdb3da1d5d84 fbshipit-source-id: 29071780f00fcddb0b1886a46caabdb3da1d5d84
2016-04-01 13:53:12 +00:00
_eventsDispatchScheduled = NO;
}
- (void)sendAppEventWithName:(NSString *)name body:(id)body
{
[_bridge enqueueJSCall:@"RCTNativeAppEventEmitter.emit"
args:body ? @[name, body] : @[name]];
}
- (void)sendDeviceEventWithName:(NSString *)name body:(id)body
{
[_bridge enqueueJSCall:@"RCTDeviceEventEmitter.emit"
args:body ? @[name, body] : @[name]];
}
- (void)sendInputEventWithName:(NSString *)name body:(NSDictionary *)body
{
if (RCT_DEBUG) {
RCTAssert([body[@"target"] isKindOfClass:[NSNumber class]],
@"Event body dictionary must include a 'target' property containing a React tag");
}
name = RCTNormalizeInputEventName(name);
[_bridge enqueueJSCall:@"RCTEventEmitter.receiveEvent"
args:body ? @[body[@"target"], name, body] : @[body[@"target"], name]];
}
- (void)sendTextEventWithType:(RCTTextEventType)type
reactTag:(NSNumber *)reactTag
text:(NSString *)text
key:(NSString *)key
[ReactNative] TextInput bug fixes and features Summary: This introduces event counts to make sure JS doesn't set out of date values on native text inputs, which can cause dropped characters and can mess with autocomplete, and obviates the need for the input buffering which added lag and complexity to the component. Made sure to test simulated super-slow JS text event processing to make sure characters aren't dropped, as well as typing obviously correctable words and making sure autocomplete works as expected. TextInput is now a controlled input by default without causing any issues for most cases, so I removed the `controlled` prop. Fixes selection state jumping by restoring it after setting new text values, so highlighting the middle of some text in the new ReWrite example and hitting space will replace that selection with an underscore and keep the cursor at a sensible position as expected, instead of jumping to the end. Ads `maxLength` prop to support the most commonly needed syncronous behavior: preventing the user from typing too many characters. It can also be used to prevent users from continuing to type after entering special characters by changing it to the current length after a regex match. Made sure to verify it works well with pasted input (including in the middle of existing text), truncating it and collapsing the selection the same way it does on the web. Fixes bug in TextEventsExample where it wouldn't show the submit and end events, even though there were firing correctly.
2015-07-21 19:37:24 +00:00
eventCount:(NSInteger)eventCount
{
static NSString *events[] = {
@"focus",
@"blur",
@"change",
@"submitEditing",
@"endEditing",
@"keyPress"
};
NSMutableDictionary *body = [[NSMutableDictionary alloc] initWithDictionary:@{
[ReactNative] TextInput bug fixes and features Summary: This introduces event counts to make sure JS doesn't set out of date values on native text inputs, which can cause dropped characters and can mess with autocomplete, and obviates the need for the input buffering which added lag and complexity to the component. Made sure to test simulated super-slow JS text event processing to make sure characters aren't dropped, as well as typing obviously correctable words and making sure autocomplete works as expected. TextInput is now a controlled input by default without causing any issues for most cases, so I removed the `controlled` prop. Fixes selection state jumping by restoring it after setting new text values, so highlighting the middle of some text in the new ReWrite example and hitting space will replace that selection with an underscore and keep the cursor at a sensible position as expected, instead of jumping to the end. Ads `maxLength` prop to support the most commonly needed syncronous behavior: preventing the user from typing too many characters. It can also be used to prevent users from continuing to type after entering special characters by changing it to the current length after a regex match. Made sure to verify it works well with pasted input (including in the middle of existing text), truncating it and collapsing the selection the same way it does on the web. Fixes bug in TextEventsExample where it wouldn't show the submit and end events, even though there were firing correctly.
2015-07-21 19:37:24 +00:00
@"eventCount": @(eventCount),
@"target": reactTag
}];
if (text) {
body[@"text"] = text;
}
if (key) {
if (key.length == 0) {
key = @"Backspace"; // backspace
} else {
switch ([key characterAtIndex:0]) {
case '\t':
key = @"Tab";
break;
case '\n':
key = @"Enter";
default:
break;
}
}
body[@"key"] = key;
}
[self sendInputEventWithName:events[type] body:body];
}
- (void)sendEvent:(id<RCTEvent>)event
{
[_eventQueueLock lock];
NSNumber *eventID = RCTGetEventID(event);
better event emitting Summary:Previously, (mostly touch and scroll) event handling on iOS worked in a hybrid way: * All incoming coalesce-able events would be pooled and retrieved by js thread in the beginning of its frame (all of this happens on js thread) * Any non-coalesce-able event would be immediately dispatched on a js thread (triggered from main thread), and if there would be pooled coalesce-able events they would be immediately dispatched at first too. This behavior has a subtle race condition, where two events are produced (on MT) in one order and received in js in different order. See https://github.com/facebook/react-native/issues/5246#issuecomment-198326673 for further explanation of this case. The new event handling is (afaik) what Android already does. When an event comes we add it into a pool of events and dispatch a block on js thread to inform js there are events to be processed. We keep track of whether we did so, so there is at most one of these blocks waiting to be processed. When the block is executed js will process all events that are in pool at that time (NOT at time of enqueuing the block). This creates a single way of processing events and makes it impossible to process them in different order in js. The tricky part was making sure we don't coalesce events across gestures/different scrolls. Before this was achieved by knowing that gestures and scrolls start/end with non-coalesce-able event, so the pool never contained events that shouldn't be coalesced together. That "assumption" doesn't hold now. I've re-added `coalescingKey` and made touch and scroll events use it to prevent coalescing events of the same type that should remain separate in previous diffs (see dependencies). On top of it it decreases latency in events processing in case where we get only coalesce-able events. Previously these would be processed at begging of the next js frame, even when js would be free and could process them sooner. This delay is done, since they would get processed as soon as the enqueued block would run. To illustrate this improvement let's look at these two systraces. Before: https://cloud.githubusercontent.com/assets/713625/14021417/47b35b7a-f1d3-11e5-93dd-4363edfa1923.png After: https://cloud.githubusercontent.com/assets/713625/14021415/4798582a-f1d3-11e5-8715-425596e0781c.png Reviewed By: javache Differential Revision: D3092867 fb-gh-sync-id: 29071780f00fcddb0b1886a46caabdb3da1d5d84 fbshipit-source-id: 29071780f00fcddb0b1886a46caabdb3da1d5d84
2016-04-01 13:53:12 +00:00
id<RCTEvent> previousEvent = _eventQueue[eventID];
if (previousEvent) {
better event emitting Summary:Previously, (mostly touch and scroll) event handling on iOS worked in a hybrid way: * All incoming coalesce-able events would be pooled and retrieved by js thread in the beginning of its frame (all of this happens on js thread) * Any non-coalesce-able event would be immediately dispatched on a js thread (triggered from main thread), and if there would be pooled coalesce-able events they would be immediately dispatched at first too. This behavior has a subtle race condition, where two events are produced (on MT) in one order and received in js in different order. See https://github.com/facebook/react-native/issues/5246#issuecomment-198326673 for further explanation of this case. The new event handling is (afaik) what Android already does. When an event comes we add it into a pool of events and dispatch a block on js thread to inform js there are events to be processed. We keep track of whether we did so, so there is at most one of these blocks waiting to be processed. When the block is executed js will process all events that are in pool at that time (NOT at time of enqueuing the block). This creates a single way of processing events and makes it impossible to process them in different order in js. The tricky part was making sure we don't coalesce events across gestures/different scrolls. Before this was achieved by knowing that gestures and scrolls start/end with non-coalesce-able event, so the pool never contained events that shouldn't be coalesced together. That "assumption" doesn't hold now. I've re-added `coalescingKey` and made touch and scroll events use it to prevent coalescing events of the same type that should remain separate in previous diffs (see dependencies). On top of it it decreases latency in events processing in case where we get only coalesce-able events. Previously these would be processed at begging of the next js frame, even when js would be free and could process them sooner. This delay is done, since they would get processed as soon as the enqueued block would run. To illustrate this improvement let's look at these two systraces. Before: https://cloud.githubusercontent.com/assets/713625/14021417/47b35b7a-f1d3-11e5-93dd-4363edfa1923.png After: https://cloud.githubusercontent.com/assets/713625/14021415/4798582a-f1d3-11e5-8715-425596e0781c.png Reviewed By: javache Differential Revision: D3092867 fb-gh-sync-id: 29071780f00fcddb0b1886a46caabdb3da1d5d84 fbshipit-source-id: 29071780f00fcddb0b1886a46caabdb3da1d5d84
2016-04-01 13:53:12 +00:00
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;
better event emitting Summary:Previously, (mostly touch and scroll) event handling on iOS worked in a hybrid way: * All incoming coalesce-able events would be pooled and retrieved by js thread in the beginning of its frame (all of this happens on js thread) * Any non-coalesce-able event would be immediately dispatched on a js thread (triggered from main thread), and if there would be pooled coalesce-able events they would be immediately dispatched at first too. This behavior has a subtle race condition, where two events are produced (on MT) in one order and received in js in different order. See https://github.com/facebook/react-native/issues/5246#issuecomment-198326673 for further explanation of this case. The new event handling is (afaik) what Android already does. When an event comes we add it into a pool of events and dispatch a block on js thread to inform js there are events to be processed. We keep track of whether we did so, so there is at most one of these blocks waiting to be processed. When the block is executed js will process all events that are in pool at that time (NOT at time of enqueuing the block). This creates a single way of processing events and makes it impossible to process them in different order in js. The tricky part was making sure we don't coalesce events across gestures/different scrolls. Before this was achieved by knowing that gestures and scrolls start/end with non-coalesce-able event, so the pool never contained events that shouldn't be coalesced together. That "assumption" doesn't hold now. I've re-added `coalescingKey` and made touch and scroll events use it to prevent coalescing events of the same type that should remain separate in previous diffs (see dependencies). On top of it it decreases latency in events processing in case where we get only coalesce-able events. Previously these would be processed at begging of the next js frame, even when js would be free and could process them sooner. This delay is done, since they would get processed as soon as the enqueued block would run. To illustrate this improvement let's look at these two systraces. Before: https://cloud.githubusercontent.com/assets/713625/14021417/47b35b7a-f1d3-11e5-93dd-4363edfa1923.png After: https://cloud.githubusercontent.com/assets/713625/14021415/4798582a-f1d3-11e5-8715-425596e0781c.png Reviewed By: javache Differential Revision: D3092867 fb-gh-sync-id: 29071780f00fcddb0b1886a46caabdb3da1d5d84 fbshipit-source-id: 29071780f00fcddb0b1886a46caabdb3da1d5d84
2016-04-01 13:53:12 +00:00
if (!_eventsDispatchScheduled) {
_eventsDispatchScheduled = YES;
[_bridge dispatchBlock:^{
[self flushEventsQueue];
} queue:RCTJSThread];
}
[_eventQueueLock unlock];
}
- (void)dispatchEvent:(id<RCTEvent>)event
{
[_bridge enqueueJSCall:[[event class] moduleDotMethod] args:[event arguments]];
}
- (dispatch_queue_t)methodQueue
{
return RCTJSThread;
}
better event emitting Summary:Previously, (mostly touch and scroll) event handling on iOS worked in a hybrid way: * All incoming coalesce-able events would be pooled and retrieved by js thread in the beginning of its frame (all of this happens on js thread) * Any non-coalesce-able event would be immediately dispatched on a js thread (triggered from main thread), and if there would be pooled coalesce-able events they would be immediately dispatched at first too. This behavior has a subtle race condition, where two events are produced (on MT) in one order and received in js in different order. See https://github.com/facebook/react-native/issues/5246#issuecomment-198326673 for further explanation of this case. The new event handling is (afaik) what Android already does. When an event comes we add it into a pool of events and dispatch a block on js thread to inform js there are events to be processed. We keep track of whether we did so, so there is at most one of these blocks waiting to be processed. When the block is executed js will process all events that are in pool at that time (NOT at time of enqueuing the block). This creates a single way of processing events and makes it impossible to process them in different order in js. The tricky part was making sure we don't coalesce events across gestures/different scrolls. Before this was achieved by knowing that gestures and scrolls start/end with non-coalesce-able event, so the pool never contained events that shouldn't be coalesced together. That "assumption" doesn't hold now. I've re-added `coalescingKey` and made touch and scroll events use it to prevent coalescing events of the same type that should remain separate in previous diffs (see dependencies). On top of it it decreases latency in events processing in case where we get only coalesce-able events. Previously these would be processed at begging of the next js frame, even when js would be free and could process them sooner. This delay is done, since they would get processed as soon as the enqueued block would run. To illustrate this improvement let's look at these two systraces. Before: https://cloud.githubusercontent.com/assets/713625/14021417/47b35b7a-f1d3-11e5-93dd-4363edfa1923.png After: https://cloud.githubusercontent.com/assets/713625/14021415/4798582a-f1d3-11e5-8715-425596e0781c.png Reviewed By: javache Differential Revision: D3092867 fb-gh-sync-id: 29071780f00fcddb0b1886a46caabdb3da1d5d84 fbshipit-source-id: 29071780f00fcddb0b1886a46caabdb3da1d5d84
2016-04-01 13:53:12 +00:00
// js thread only
- (void)flushEventsQueue
{
[_eventQueueLock lock];
NSDictionary *eventQueue = _eventQueue;
_eventQueue = [NSMutableDictionary new];
better event emitting Summary:Previously, (mostly touch and scroll) event handling on iOS worked in a hybrid way: * All incoming coalesce-able events would be pooled and retrieved by js thread in the beginning of its frame (all of this happens on js thread) * Any non-coalesce-able event would be immediately dispatched on a js thread (triggered from main thread), and if there would be pooled coalesce-able events they would be immediately dispatched at first too. This behavior has a subtle race condition, where two events are produced (on MT) in one order and received in js in different order. See https://github.com/facebook/react-native/issues/5246#issuecomment-198326673 for further explanation of this case. The new event handling is (afaik) what Android already does. When an event comes we add it into a pool of events and dispatch a block on js thread to inform js there are events to be processed. We keep track of whether we did so, so there is at most one of these blocks waiting to be processed. When the block is executed js will process all events that are in pool at that time (NOT at time of enqueuing the block). This creates a single way of processing events and makes it impossible to process them in different order in js. The tricky part was making sure we don't coalesce events across gestures/different scrolls. Before this was achieved by knowing that gestures and scrolls start/end with non-coalesce-able event, so the pool never contained events that shouldn't be coalesced together. That "assumption" doesn't hold now. I've re-added `coalescingKey` and made touch and scroll events use it to prevent coalescing events of the same type that should remain separate in previous diffs (see dependencies). On top of it it decreases latency in events processing in case where we get only coalesce-able events. Previously these would be processed at begging of the next js frame, even when js would be free and could process them sooner. This delay is done, since they would get processed as soon as the enqueued block would run. To illustrate this improvement let's look at these two systraces. Before: https://cloud.githubusercontent.com/assets/713625/14021417/47b35b7a-f1d3-11e5-93dd-4363edfa1923.png After: https://cloud.githubusercontent.com/assets/713625/14021415/4798582a-f1d3-11e5-8715-425596e0781c.png Reviewed By: javache Differential Revision: D3092867 fb-gh-sync-id: 29071780f00fcddb0b1886a46caabdb3da1d5d84 fbshipit-source-id: 29071780f00fcddb0b1886a46caabdb3da1d5d84
2016-04-01 13:53:12 +00:00
_eventsDispatchScheduled = NO;
[_eventQueueLock unlock];
for (id<RCTEvent> event in eventQueue.allValues) {
[self dispatchEvent:event];
}
}
@end