diff --git a/ReactWindows/ReactNative.Tests/Bridge/Queue/MessageQueueThreadTests.cs b/ReactWindows/ReactNative.Tests/Bridge/Queue/MessageQueueThreadTests.cs index ab204c66f92..be01d191d69 100644 --- a/ReactWindows/ReactNative.Tests/Bridge/Queue/MessageQueueThreadTests.cs +++ b/ReactWindows/ReactNative.Tests/Bridge/Queue/MessageQueueThreadTests.cs @@ -26,6 +26,7 @@ public void MessageQueueThread_ArgumentChecks() public void MessageQueueThread_CreateUiThread_ThrowsNotSupported() { AssertEx.Throws(() => MessageQueueThreadSpec.Create("ui", MessageQueueThreadKind.DispatcherThread)); + AssertEx.Throws(() => MessageQueueThreadSpec.Create("layout", MessageQueueThreadKind.LayoutThread)); } [TestMethod] @@ -33,12 +34,14 @@ public async Task MessageQueueThread_IsOnThread() { var thrown = 0; var uiThread = await CallOnDispatcherAsync(() => MessageQueueThread.Create(MessageQueueThreadSpec.DispatcherThreadSpec, ex => thrown++)); + var layoutThread = await CallOnDispatcherAsync(() => MessageQueueThread.Create(MessageQueueThreadSpec.LayoutThreadSpec, ex => thrown++)); var backgroundThread = MessageQueueThread.Create(MessageQueueThreadSpec.Create("background", MessageQueueThreadKind.BackgroundSingleThread), ex => thrown++); var taskPoolThread = MessageQueueThread.Create(MessageQueueThreadSpec.Create("any", MessageQueueThreadKind.BackgroundAnyThread), ex => thrown++); var queueThreads = new[] { uiThread, + layoutThread, backgroundThread, taskPoolThread }; @@ -69,12 +72,14 @@ public async Task MessageQueueThread_HandlesException() }); var uiThread = await CallOnDispatcherAsync(() => MessageQueueThread.Create(MessageQueueThreadSpec.DispatcherThreadSpec, handler)); + var layoutThread = await CallOnDispatcherAsync(() => MessageQueueThread.Create(MessageQueueThreadSpec.LayoutThreadSpec, handler)); var backgroundThread = MessageQueueThread.Create(MessageQueueThreadSpec.Create("background", MessageQueueThreadKind.BackgroundSingleThread), handler); var taskPoolThread = MessageQueueThread.Create(MessageQueueThreadSpec.Create("any", MessageQueueThreadKind.BackgroundAnyThread), handler); var queueThreads = new[] { uiThread, + layoutThread, backgroundThread, taskPoolThread }; @@ -90,9 +95,10 @@ public async Task MessageQueueThread_HandlesException() [TestMethod] public async Task MessageQueueThread_OneAtATime() { - var uiThread = await CallOnDispatcherAsync(() => MessageQueueThread.Create(MessageQueueThreadSpec.DispatcherThreadSpec, ex => { Assert.Fail(); })); - var backgroundThread = MessageQueueThread.Create(MessageQueueThreadSpec.Create("background", MessageQueueThreadKind.BackgroundSingleThread), ex => { Assert.Fail(); }); - var taskPoolThread = MessageQueueThread.Create(MessageQueueThreadSpec.Create("any", MessageQueueThreadKind.BackgroundAnyThread), ex => { Assert.Fail(); }); + var uiThread = await CallOnDispatcherAsync(() => MessageQueueThread.Create(MessageQueueThreadSpec.DispatcherThreadSpec, ex => Assert.Fail())); + var layoutThread = await CallOnDispatcherAsync(() => MessageQueueThread.Create(MessageQueueThreadSpec.LayoutThreadSpec, ex => Assert.Fail())); + var backgroundThread = MessageQueueThread.Create(MessageQueueThreadSpec.Create("background", MessageQueueThreadKind.BackgroundSingleThread), ex => Assert.Fail()); + var taskPoolThread = MessageQueueThread.Create(MessageQueueThreadSpec.Create("any", MessageQueueThreadKind.BackgroundAnyThread), ex => Assert.Fail()); var enter = new AutoResetEvent(false); var exit = new AutoResetEvent(false); @@ -100,6 +106,7 @@ public async Task MessageQueueThread_OneAtATime() var queueThreads = new[] { uiThread, + layoutThread, backgroundThread, taskPoolThread }; @@ -124,13 +131,15 @@ public async Task MessageQueueThread_OneAtATime() [TestMethod] public async Task MessageQueueThread_Dispose() { - var uiThread = await CallOnDispatcherAsync(() => MessageQueueThread.Create(MessageQueueThreadSpec.DispatcherThreadSpec, ex => { Assert.Fail(); })); - var backgroundThread = MessageQueueThread.Create(MessageQueueThreadSpec.Create("background", MessageQueueThreadKind.BackgroundSingleThread), ex => { Assert.Fail(); }); - var taskPoolThread = MessageQueueThread.Create(MessageQueueThreadSpec.Create("any", MessageQueueThreadKind.BackgroundAnyThread), ex => { Assert.Fail(); }); + var uiThread = await CallOnDispatcherAsync(() => MessageQueueThread.Create(MessageQueueThreadSpec.DispatcherThreadSpec, ex => Assert.Fail())); + var layoutThread = await CallOnDispatcherAsync(() => MessageQueueThread.Create(MessageQueueThreadSpec.LayoutThreadSpec, ex => Assert.Fail())); + var backgroundThread = MessageQueueThread.Create(MessageQueueThreadSpec.Create("background", MessageQueueThreadKind.BackgroundSingleThread), ex => Assert.Fail()); + var taskPoolThread = MessageQueueThread.Create(MessageQueueThreadSpec.Create("any", MessageQueueThreadKind.BackgroundAnyThread), ex => Assert.Fail()); var queueThreads = new[] { uiThread, + layoutThread, backgroundThread, taskPoolThread }; diff --git a/ReactWindows/ReactNative/Bridge/Queue/IReactQueueConfiguration.cs b/ReactWindows/ReactNative/Bridge/Queue/IReactQueueConfiguration.cs index a38d2775b99..42b490c0064 100644 --- a/ReactWindows/ReactNative/Bridge/Queue/IReactQueueConfiguration.cs +++ b/ReactWindows/ReactNative/Bridge/Queue/IReactQueueConfiguration.cs @@ -15,6 +15,11 @@ public interface IReactQueueConfiguration : IDisposable /// IMessageQueueThread DispatcherQueueThread { get; } + /// + /// The layout thread. + /// + IMessageQueueThread LayoutQueueThread { get; } + /// /// The native modules thread. /// diff --git a/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThread.cs b/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThread.cs index eea5e898cb1..3e5052884f2 100644 --- a/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThread.cs +++ b/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThread.cs @@ -7,6 +7,7 @@ using System.Reactive.Subjects; using System.Threading; using System.Threading.Tasks; +using Windows.ApplicationModel.Core; using Windows.Foundation; using Windows.System.Threading; using Windows.UI.Core; @@ -124,7 +125,9 @@ public static MessageQueueThread Create( switch (spec.Kind) { case MessageQueueThreadKind.DispatcherThread: - return new DispatcherMessageQueueThread(spec.Name, handler); + case MessageQueueThreadKind.LayoutThread: + var isSecondary = spec.Kind == MessageQueueThreadKind.LayoutThread; + return new DispatcherMessageQueueThread(spec.Name, handler, isSecondary); case MessageQueueThreadKind.BackgroundSingleThread: return new SingleBackgroundMessageQueueThread(spec.Name, handler); case MessageQueueThreadKind.BackgroundAnyThread: @@ -137,20 +140,28 @@ public static MessageQueueThread Create( class DispatcherMessageQueueThread : MessageQueueThread { + private static readonly CoreApplicationView s_secondaryView = CoreApplication.CreateNewView(); private static readonly IObserver s_nop = Observer.Create(_ => { }); + private readonly bool _isSecondary; private readonly Subject _actionSubject; private readonly IDisposable _subscription; private IObserver _actionObserver; - public DispatcherMessageQueueThread(string name, Action handler) + public DispatcherMessageQueueThread(string name, Action handler, bool isSecondary) : base(name) { + _isSecondary = isSecondary; _actionSubject = new Subject(); _actionObserver = _actionSubject; + + var dispatcher = isSecondary + ? s_secondaryView.Dispatcher + : CoreApplication.GetCurrentView().Dispatcher; + _subscription = _actionSubject - .ObserveOnDispatcher() + .ObserveOn(dispatcher) .Subscribe(action => { try @@ -171,7 +182,7 @@ protected override void Enqueue(Action action) protected override bool IsOnThreadCore() { - return CoreWindow.GetForCurrentThread().Dispatcher != null; + return GetApplicationView() != null; } protected override void Dispose(bool disposing) @@ -181,6 +192,20 @@ protected override void Dispose(bool disposing) _actionSubject.Dispose(); _subscription.Dispose(); } + + private CoreApplicationView GetApplicationView() + { + if (_isSecondary && s_secondaryView.Dispatcher.HasThreadAccess) + { + return s_secondaryView; + } + else if (!_isSecondary) + { + return CoreApplication.GetCurrentView(); + } + + return null; + } } class SingleBackgroundMessageQueueThread : MessageQueueThread diff --git a/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadKind.cs b/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadKind.cs index 1a6f582dba5..f6334e01786 100644 --- a/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadKind.cs +++ b/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadKind.cs @@ -9,6 +9,11 @@ public enum MessageQueueThreadKind /// Dispatcher thread type. /// DispatcherThread, + + /// + /// Layout thread type. + /// + LayoutThread, /// /// Single background thread type. diff --git a/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadSpec.cs b/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadSpec.cs index add7d2d10ad..4f94fe1fe0c 100644 --- a/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadSpec.cs +++ b/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadSpec.cs @@ -28,6 +28,11 @@ private MessageQueueThreadSpec(MessageQueueThreadKind kind, string name) /// public static MessageQueueThreadSpec DispatcherThreadSpec { get; } = new MessageQueueThreadSpec(MessageQueueThreadKind.DispatcherThread, "main_ui"); + /// + /// Singleton layout specification. + /// + public static MessageQueueThreadSpec LayoutThreadSpec { get; } = new MessageQueueThreadSpec(MessageQueueThreadKind.LayoutThread, "layout"); + /// /// Factory for creating s. /// @@ -38,7 +43,12 @@ public static MessageQueueThreadSpec Create(string name, MessageQueueThreadKind { if (kind == MessageQueueThreadKind.DispatcherThread) { - throw new NotSupportedException("Use the singleton MainUiThreadSpec instance."); + throw new NotSupportedException("Use the singleton DispatcherThreadSpec instance."); + } + + if (kind == MessageQueueThreadKind.LayoutThread) + { + throw new NotSupportedException("Use the singleton LayoutThreadSpec instance."); } return new MessageQueueThreadSpec(kind, name); diff --git a/ReactWindows/ReactNative/Bridge/Queue/ReactQueueConfiguration.cs b/ReactWindows/ReactNative/Bridge/Queue/ReactQueueConfiguration.cs index c58e175a7be..3637e7fa3ba 100644 --- a/ReactWindows/ReactNative/Bridge/Queue/ReactQueueConfiguration.cs +++ b/ReactWindows/ReactNative/Bridge/Queue/ReactQueueConfiguration.cs @@ -11,15 +11,18 @@ namespace ReactNative.Bridge.Queue class ReactQueueConfiguration : IReactQueueConfiguration { private readonly MessageQueueThread _dispatcherQueueThread; + private readonly MessageQueueThread _layoutQueueThread; private readonly MessageQueueThread _nativeModulesQueueThread; private readonly MessageQueueThread _jsQueueThread; private ReactQueueConfiguration( MessageQueueThread dispatcherQueueThread, + MessageQueueThread layoutQueueThread, MessageQueueThread nativeModulesQueueThread, MessageQueueThread jsQueueThread) { _dispatcherQueueThread = dispatcherQueueThread; + _layoutQueueThread = layoutQueueThread; _nativeModulesQueueThread = nativeModulesQueueThread; _jsQueueThread = jsQueueThread; } @@ -35,6 +38,17 @@ public IMessageQueueThread DispatcherQueueThread } } + /// + /// The layout queue thread. + /// + public IMessageQueueThread LayoutQueueThread + { + get + { + return _layoutQueueThread; + } + } + /// /// The native modules thread. /// @@ -67,6 +81,7 @@ public IMessageQueueThread JavaScriptQueueThread public void Dispose() { _dispatcherQueueThread.Dispose(); + _layoutQueueThread.Dispose(); _nativeModulesQueueThread.Dispose(); _jsQueueThread.Dispose(); } @@ -84,6 +99,9 @@ public static ReactQueueConfiguration Create( var dispatcherThreadSpec = MessageQueueThreadSpec.DispatcherThreadSpec; var dispatcherThread = MessageQueueThread.Create(dispatcherThreadSpec, exceptionHandler); + var layoutThreadSpec = MessageQueueThreadSpec.LayoutThreadSpec; + var layoutThread = MessageQueueThread.Create(layoutThreadSpec, exceptionHandler); + var jsThread = spec.JSQueueThreadSpec != dispatcherThreadSpec ? MessageQueueThread.Create(spec.JSQueueThreadSpec, exceptionHandler) : dispatcherThread; @@ -92,7 +110,7 @@ public static ReactQueueConfiguration Create( ? MessageQueueThread.Create(spec.NativeModulesQueueThreadSpec, exceptionHandler) : dispatcherThread; - return new ReactQueueConfiguration(dispatcherThread, nativeModulesThread, jsThread); + return new ReactQueueConfiguration(dispatcherThread, layoutThread, nativeModulesThread, jsThread); } } } diff --git a/ReactWindows/ReactNative/Bridge/Queue/ReactQueueConfigurationSpec.cs b/ReactWindows/ReactNative/Bridge/Queue/ReactQueueConfigurationSpec.cs index 22466797d05..ea5d7e267b8 100644 --- a/ReactWindows/ReactNative/Bridge/Queue/ReactQueueConfigurationSpec.cs +++ b/ReactWindows/ReactNative/Bridge/Queue/ReactQueueConfigurationSpec.cs @@ -17,6 +17,14 @@ private ReactQueueConfigurationSpec( JSQueueThreadSpec = jsQueueThreadSpec; } + /// + /// The Layout specification. + /// + public MessageQueueThreadSpec LayoutQueueThreadSpec + { + get; + } + /// /// The native modules specification. /// @@ -32,7 +40,7 @@ public MessageQueueThreadSpec JSQueueThreadSpec { get; } - + /// /// The default instance. /// @@ -42,8 +50,8 @@ private static ReactQueueConfigurationSpec CreateDefault() { return new Builder() { - JSQueueThreadSpec = MessageQueueThreadSpec.Create("js", MessageQueueThreadKind.BackgroundSingleThread), NativeModulesQueueThreadSpec = MessageQueueThreadSpec.Create("native_modules", MessageQueueThreadKind.BackgroundAnyThread), + JSQueueThreadSpec = MessageQueueThreadSpec.Create("js", MessageQueueThreadKind.BackgroundSingleThread), } .Build(); } @@ -81,7 +89,7 @@ public MessageQueueThreadSpec JSQueueThreadSpec { if (_jsQueueThreadSpec != null) { - throw new InvalidOperationException("Setting native modules queue thread spec multiple times!"); + throw new InvalidOperationException("Setting JavaScript queue thread spec multiple times!"); } _jsQueueThreadSpec = value; diff --git a/ReactWindows/ReactNative/Bridge/ReactContext.cs b/ReactWindows/ReactNative/Bridge/ReactContext.cs index b18799b8831..1218a6c54f9 100644 --- a/ReactWindows/ReactNative/Bridge/ReactContext.cs +++ b/ReactWindows/ReactNative/Bridge/ReactContext.cs @@ -207,8 +207,8 @@ public bool IsOnDispatcherQueueThread() } /// - /// Asserts that the current thread is on the React instance native - /// modules queue thread. + /// Asserts that the current thread is on the React instance dispatcher + /// queue thread. /// public void AssertOnDispatcherQueueThread() { @@ -226,6 +226,40 @@ public void RunOnDispatcherQueueThread(Action action) _reactInstance.QueueConfiguration.DispatcherQueueThread.RunOnQueue(action); } + /// + /// Checks if the current thread is on the React instance layout + /// queue thread. + /// + /// + /// true if the call is from the layout queue thread, + /// false otherwise. + /// + public bool IsOnLayoutQueueThread() + { + AssertReactInstance(); + return _reactInstance.QueueConfiguration.LayoutQueueThread.IsOnThread(); + } + + /// + /// Asserts that the current thread is on the React instance layout + /// queue thread. + /// + public void AssertOnLayoutQueueThread() + { + AssertReactInstance(); + _reactInstance.QueueConfiguration.LayoutQueueThread.AssertOnThread(); + } + + /// + /// Enqueues an action on the layout queue thread. + /// + /// The action. + public void RunOnLayoutQueueThread(Action action) + { + AssertReactInstance(); + _reactInstance.QueueConfiguration.LayoutQueueThread.RunOnQueue(action); + } + /// /// Checks if the current thread is on the React instance /// JavaScript queue thread. diff --git a/ReactWindows/ReactNative/UIManager/UIManagerModule.cs b/ReactWindows/ReactNative/UIManager/UIManagerModule.cs index a9ac8124ba8..290de5a8aca 100644 --- a/ReactWindows/ReactNative/UIManager/UIManagerModule.cs +++ b/ReactWindows/ReactNative/UIManager/UIManagerModule.cs @@ -112,11 +112,11 @@ public int AddMeasuredRootView(SizeMonitoringCanvas rootView) var newWidth = args.NewSize.Width; var newHeight = args.NewSize.Height; - Context.RunOnNativeModulesQueueThread(() => + Context.RunOnLayoutQueueThread(() => { if (currentCount == resizeCount) { - Context.AssertOnNativeModulesQueueThread(); + Context.AssertOnLayoutQueueThread(); _uiImplementation.UpdateRootNodeSize(tag, newWidth, newHeight, _eventDispatcher); } }); @@ -134,7 +134,8 @@ public int AddMeasuredRootView(SizeMonitoringCanvas rootView) [ReactMethod] public void removeRootView(int rootViewTag) { - _uiImplementation.RemoveRootView(rootViewTag); + Context.RunOnLayoutQueueThread(() => + _uiImplementation.RemoveRootView(rootViewTag)); } /// @@ -147,7 +148,8 @@ public void removeRootView(int rootViewTag) [ReactMethod] public void createView(int tag, string className, int rootViewTag, JObject props) { - _uiImplementation.CreateView(tag, className, rootViewTag, props); + Context.RunOnLayoutQueueThread(() => + _uiImplementation.CreateView(tag, className, rootViewTag, props)); } /// @@ -159,7 +161,8 @@ public void createView(int tag, string className, int rootViewTag, JObject props [ReactMethod] public void updateView(int tag, string className, JObject props) { - _uiImplementation.UpdateView(tag, className, props); + Context.RunOnLayoutQueueThread(() => + _uiImplementation.UpdateView(tag, className, props)); } /// @@ -191,13 +194,14 @@ public void manageChildren( int[] addAtIndices, int[] removeFrom) { - _uiImplementation.ManageChildren( - viewTag, - moveFrom, - moveTo, - addChildTags, - addAtIndices, - removeFrom); + Context.RunOnLayoutQueueThread(() => + _uiImplementation.ManageChildren( + viewTag, + moveFrom, + moveTo, + addChildTags, + addAtIndices, + removeFrom)); } /// @@ -213,7 +217,8 @@ public void setChildren( int viewTag, int[] childrenTags) { - _uiImplementation.SetChildren(viewTag, childrenTags); + Context.RunOnLayoutQueueThread(() => + _uiImplementation.SetChildren(viewTag, childrenTags)); } /// @@ -229,7 +234,8 @@ public void setChildren( [ReactMethod] public void replaceExistingNonRootView(int oldTag, int newTag) { - _uiImplementation.ReplaceExistingNonRootView(oldTag, newTag); + Context.RunOnLayoutQueueThread(() => + _uiImplementation.ReplaceExistingNonRootView(oldTag, newTag)); } /// @@ -240,7 +246,8 @@ public void replaceExistingNonRootView(int oldTag, int newTag) [ReactMethod] public void removeSubviewsFromContainerWithID(int containerTag) { - _uiImplementation.RemoveSubviewsFromContainerWithID(containerTag); + Context.RunOnLayoutQueueThread(() => + _uiImplementation.RemoveSubviewsFromContainerWithID(containerTag)); } /// @@ -252,7 +259,8 @@ public void removeSubviewsFromContainerWithID(int containerTag) [ReactMethod] public void measure(int reactTag, ICallback callback) { - _uiImplementation.Measure(reactTag, callback); + Context.RunOnLayoutQueueThread(() => + _uiImplementation.Measure(reactTag, callback)); } /// @@ -264,7 +272,8 @@ public void measure(int reactTag, ICallback callback) [ReactMethod] public void measureInWindow(int reactTag, ICallback callback) { - _uiImplementation.MeasureInWindow(reactTag, callback); + Context.RunOnLayoutQueueThread(() => + _uiImplementation.MeasureInWindow(reactTag, callback)); } /// @@ -289,7 +298,8 @@ public void measureLayout( ICallback errorCallback, ICallback successCallback) { - _uiImplementation.MeasureLayout(tag, ancestorTag, errorCallback, successCallback); + Context.RunOnLayoutQueueThread(() => + _uiImplementation.MeasureLayout(tag, ancestorTag, errorCallback, successCallback)); } /// @@ -312,7 +322,8 @@ public void measureLayoutRelativeToParent( ICallback errorCallback, ICallback successCallback) { - _uiImplementation.MeasureLayoutRelativeToParent(tag, errorCallback, successCallback); + Context.RunOnLayoutQueueThread(() => + _uiImplementation.MeasureLayoutRelativeToParent(tag, errorCallback, successCallback)); } /// @@ -335,11 +346,12 @@ public void findSubviewIn( JArray point, ICallback callback) { - _uiImplementation.FindSubviewIn( - reactTag, - point[0].Value(), - point[1].Value(), - callback); + Context.RunOnLayoutQueueThread(() => + _uiImplementation.FindSubviewIn( + reactTag, + point[0].Value(), + point[1].Value(), + callback)); } /// @@ -352,7 +364,8 @@ public void findSubviewIn( [ReactMethod] public void setJSResponder(int reactTag, bool blockNativeResponder) { - _uiImplementation.SetJavaScriptResponder(reactTag, blockNativeResponder); + Context.RunOnLayoutQueueThread(() => + _uiImplementation.SetJavaScriptResponder(reactTag, blockNativeResponder)); } /// @@ -361,7 +374,8 @@ public void setJSResponder(int reactTag, bool blockNativeResponder) [ReactMethod] public void clearJSResponder() { - _uiImplementation.ClearJavaScriptResponder(); + Context.RunOnLayoutQueueThread(() => + _uiImplementation.ClearJavaScriptResponder()); } /// @@ -373,7 +387,8 @@ public void clearJSResponder() [ReactMethod] public void dispatchViewManagerCommand(int reactTag, int commandId, JArray commandArgs) { - _uiImplementation.DispatchViewManagerCommand(reactTag, commandId, commandArgs); + Context.RunOnLayoutQueueThread(() => + _uiImplementation.DispatchViewManagerCommand(reactTag, commandId, commandArgs)); } /// @@ -395,7 +410,8 @@ public void dispatchViewManagerCommand(int reactTag, int commandId, JArray comma [ReactMethod] public void showPopupMenu(int reactTag, string[] items, ICallback error, ICallback success) { - _uiImplementation.ShowPopupMenu(reactTag, items, error, success); + Context.RunOnLayoutQueueThread(() => + _uiImplementation.ShowPopupMenu(reactTag, items, error, success)); } /// @@ -416,7 +432,8 @@ public void showPopupMenu(int reactTag, string[] items, ICallback error, ICallba [ReactMethod] public void configureNextLayoutAnimation(JObject config, ICallback success, ICallback error) { - _uiImplementation.ConfigureNextLayoutAnimation(config, success, error); + Context.RunOnLayoutQueueThread(() => + _uiImplementation.ConfigureNextLayoutAnimation(config, success, error)); } #endregion @@ -464,12 +481,15 @@ public void OnBatchComplete() { var batchId = _batchId++; - using (Tracer.Trace(Tracer.TRACE_TAG_REACT_BRIDGE, "onBatchCompleteUI") - .With("BatchId", batchId) - .Start()) + Context.RunOnLayoutQueueThread(() => { - _uiImplementation.DispatchViewUpdates(_eventDispatcher, batchId); - } + using (Tracer.Trace(Tracer.TRACE_TAG_REACT_BRIDGE, "onBatchCompleteUI") + .With("BatchId", batchId) + .Start()) + { + _uiImplementation.DispatchViewUpdates(_eventDispatcher, batchId); + } + }); } #endregion diff --git a/ReactWindows/ReactNative/UIManager/UIViewOperationQueue.cs b/ReactWindows/ReactNative/UIManager/UIViewOperationQueue.cs index a22eba8cf6c..f9e14ff124d 100644 --- a/ReactWindows/ReactNative/UIManager/UIViewOperationQueue.cs +++ b/ReactWindows/ReactNative/UIManager/UIViewOperationQueue.cs @@ -47,7 +47,7 @@ public UIViewOperationQueue(ReactContext reactContext, NativeViewHierarchyManage /// public bool IsEmpty() { - lock (_operations) + lock (_gate) { return _operations.Count == 0; } @@ -369,14 +369,14 @@ public void OnShutdown() /// The batch identifier. internal void DispatchViewUpdates(int batchId) { - var operations = _operations.Count == 0 ? null : _operations; - if (operations != null) - { - _operations = new List(); - } - lock (_gate) { + var operations = _operations.Count == 0 ? null : _operations; + if (operations != null) + { + _operations = new List(); + } + _batches.Add(() => { using (Tracer.Trace(Tracer.TRACE_TAG_REACT_BRIDGE, "DispatchUI") diff --git a/ReactWindows/ReactNative/Views/Text/ReactTextShadowNode.cs b/ReactWindows/ReactNative/Views/Text/ReactTextShadowNode.cs index 826c6d1a9a1..b78b30c2407 100644 --- a/ReactWindows/ReactNative/Views/Text/ReactTextShadowNode.cs +++ b/ReactWindows/ReactNative/Views/Text/ReactTextShadowNode.cs @@ -39,20 +39,6 @@ public ReactTextShadowNode() MeasureFunction = MeasureText; } - /// - /// Instantiates the . - /// - /// - /// A flag signaling whether or not the node is the root node. - /// - public ReactTextShadowNode(bool isRoot) - { - if (isRoot) - { - MeasureFunction = MeasureText; - } - } - /// /// Sets the font size for the node. /// @@ -201,41 +187,36 @@ protected override void MarkUpdated() private static MeasureOutput MeasureText(CSSNode node, float width, CSSMeasureMode widthMode, float height, CSSMeasureMode heightMode) { - // This is not a terribly efficient way of projecting the height of - // the text elements. It requires that we have access to the - // dispatcher in order to do measurement, which, for obvious - // reasons, can cause perceived performance issues as it will block - // the UI thread from handling other work. - // - // TODO: determine another way to measure text elements. - var task = DispatcherHelpers.CallOnDispatcher(() => - { - var textBlock = new RichTextBlock - { - TextWrapping = TextWrapping.Wrap, - TextAlignment = TextAlignment.DetectFromContent, - TextTrimming = TextTrimming.CharacterEllipsis, - }; + // TODO: Measure text with DirectWrite or other API that does not + // require dispatcher access. Currently, we're instantiating a + // second CoreApplicationView (that is never activated) and using + // its Dispatcher thread to calculate layout. - var textNode = (ReactTextShadowNode)node; - textNode.UpdateTextBlockCore(textBlock, true); + var textNode = (ReactTextShadowNode)node; + textNode.ThemedContext.AssertOnLayoutQueueThread(); - var block = new Paragraph(); - foreach (var child in textNode.Children) - { - block.Inlines.Add(ReactInlineShadowNodeVisitor.Apply(child)); - } - textBlock.Blocks.Add(block); + var textBlock = new RichTextBlock + { + TextWrapping = TextWrapping.Wrap, + TextAlignment = TextAlignment.DetectFromContent, + TextTrimming = TextTrimming.CharacterEllipsis, + }; + + textNode.UpdateTextBlockCore(textBlock, true); - var normalizedWidth = CSSConstants.IsUndefined(width) ? double.PositiveInfinity : width; - var normalizedHeight = CSSConstants.IsUndefined(height) ? double.PositiveInfinity : height; - textBlock.Measure(new Size(normalizedWidth, normalizedHeight)); - return new MeasureOutput( - (float)textBlock.DesiredSize.Width, - (float)textBlock.DesiredSize.Height); - }); + var block = new Paragraph(); + foreach (var child in textNode.Children) + { + block.Inlines.Add(ReactInlineShadowNodeVisitor.Apply(child)); + } + textBlock.Blocks.Add(block); - return task.Result; + var normalizedWidth = CSSConstants.IsUndefined(width) ? double.PositiveInfinity : width; + var normalizedHeight = CSSConstants.IsUndefined(height) ? double.PositiveInfinity : height; + textBlock.Measure(new Size(normalizedWidth, normalizedHeight)); + return new MeasureOutput( + (float)textBlock.DesiredSize.Width, + (float)textBlock.DesiredSize.Height); } /// diff --git a/ReactWindows/ReactNative/Views/Text/ReactTextViewManager.cs b/ReactWindows/ReactNative/Views/Text/ReactTextViewManager.cs index a46ee3ef435..f4506ca177d 100644 --- a/ReactWindows/ReactNative/Views/Text/ReactTextViewManager.cs +++ b/ReactWindows/ReactNative/Views/Text/ReactTextViewManager.cs @@ -77,7 +77,7 @@ public override void AddView(RichTextBlock parent, DependencyObject child, int i /// The shadow node instance. public override ReactTextShadowNode CreateShadowNodeInstance() { - return new ReactTextShadowNode(true); + return new ReactTextShadowNode(); } /// diff --git a/ReactWindows/ReactNative/Views/TextInput/ReactTextInputShadowNode.cs b/ReactWindows/ReactNative/Views/TextInput/ReactTextInputShadowNode.cs index 477deee340f..23086a4db9b 100644 --- a/ReactWindows/ReactNative/Views/TextInput/ReactTextInputShadowNode.cs +++ b/ReactWindows/ReactNative/Views/TextInput/ReactTextInputShadowNode.cs @@ -250,6 +250,7 @@ private float[] GetComputedPadding() private static MeasureOutput MeasureTextInput(CSSNode node, float width, CSSMeasureMode widthMode, float height, CSSMeasureMode heightMode) { var textInputNode = (ReactTextInputShadowNode)node; + textInputNode.ThemedContext.AssertOnLayoutQueueThread(); textInputNode._computedPadding = textInputNode.GetComputedPadding(); var borderLeftWidth = textInputNode.GetBorder(CSSSpacingType.Left); @@ -263,43 +264,35 @@ private static MeasureOutput MeasureTextInput(CSSNode node, float width, CSSMeas - (CSSConstants.IsUndefined(borderRightWidth) ? 0 : borderRightWidth)); var normalizedHeight = Math.Max(0, CSSConstants.IsUndefined(height) ? double.PositiveInfinity : height); - // This is not a terribly efficient way of projecting the height of - // the text elements. It requires that we have access to the - // dispatcher in order to do measurement, which, for obvious - // reasons, can cause perceived performance issues as it will block - // the UI thread from handling other work. - // - // TODO: determine another way to measure text elements. - var task = DispatcherHelpers.CallOnDispatcher(() => - { - var textNode = (ReactTextInputShadowNode)node; - - var textBlock = new TextBlock - { - TextWrapping = TextWrapping.Wrap, - }; + // TODO: Measure text with DirectWrite or other API that does not + // require dispatcher access. Currently, we're instantiating a + // second CoreApplicationView (that is never activated) and using + // its Dispatcher thread to calculate layout. + var textNode = (ReactTextInputShadowNode)node; - var normalizedText = string.IsNullOrEmpty(textNode._text) ? " " : textNode._text; - var inline = new Run { Text = normalizedText }; - FormatInline(textNode, inline, true); + var textBlock = new TextBlock + { + TextWrapping = TextWrapping.Wrap, + }; - textBlock.Inlines.Add(inline); + var normalizedText = string.IsNullOrEmpty(textNode._text) ? " " : textNode._text; + var inline = new Run { Text = normalizedText }; + FormatInline(textNode, inline, true); - textBlock.Measure(new Size(normalizedWidth, normalizedHeight)); + textBlock.Inlines.Add(inline); - var borderTopWidth = textInputNode.GetBorder(CSSSpacingType.Top); - var borderBottomWidth = textInputNode.GetBorder(CSSSpacingType.Bottom); + textBlock.Measure(new Size(normalizedWidth, normalizedHeight)); - var finalizedHeight = (float)textBlock.DesiredSize.Height; - finalizedHeight += textInputNode._computedPadding[1]; - finalizedHeight += textInputNode._computedPadding[3]; - finalizedHeight += CSSConstants.IsUndefined(borderTopWidth) ? 0 : borderTopWidth; - finalizedHeight += CSSConstants.IsUndefined(borderBottomWidth) ? 0 : borderBottomWidth; + var borderTopWidth = textInputNode.GetBorder(CSSSpacingType.Top); + var borderBottomWidth = textInputNode.GetBorder(CSSSpacingType.Bottom); - return new MeasureOutput(width, finalizedHeight); - }); + var finalizedHeight = (float)textBlock.DesiredSize.Height; + finalizedHeight += textInputNode._computedPadding[1]; + finalizedHeight += textInputNode._computedPadding[3]; + finalizedHeight += CSSConstants.IsUndefined(borderTopWidth) ? 0 : borderTopWidth; + finalizedHeight += CSSConstants.IsUndefined(borderBottomWidth) ? 0 : borderBottomWidth; - return task.Result; + return new MeasureOutput(width, finalizedHeight); } ///