/// <summary> /// Detach given <paramref name="rootView"/> from the current react /// instance. This method is idempotent and can be called multiple /// times on the same <see cref="ReactRootView"/> instance. /// WARNING! Has to be called by the thread assocviated with the view. /// </summary> /// <param name="rootView">The root view.</param> public async Task DetachRootViewAsync(ReactRootView rootView) { if (rootView == null) { throw new ArgumentNullException(nameof(rootView)); } DispatcherHelpers.AssertOnDispatcher(rootView); await DispatcherHelpers.CallOnDispatcher(() => { if (_attachedRootViews.Remove(rootView)) { var currentReactContext = _currentReactContext; if (currentReactContext != null && currentReactContext.HasActiveReactInstance) { return(DetachViewFromInstanceAsync(rootView, currentReactContext.ReactInstance)); } } // return Task.CompletedTask; return(Net46.Net45.Task.CompletedTask); }, true /* inlining allowed */).Unwrap(); // await inner task }
/// <summary> /// Activate the callback for the given key. /// </summary> /// <param name="callbackKey">The callback key.</param> public void ActivateCallback(string callbackKey) { bool subscribe; lock (_gate) { var isSubscribed = Volatile.Read(ref _isSubscribed); var isSubscribing = Volatile.Read(ref _isSubscribing); var isDisposed = Volatile.Read(ref _isDisposed); subscribe = _isSubscribing = !isDisposed && _callbackKeys.Add(callbackKey) && _callbackKeys.Count == 1 && !isSubscribed && !isSubscribing; } if (subscribe) { DispatcherHelpers.RunOnDispatcher( #if WINDOWS_UWP _coreDispatcher, #else DispatcherHelpers.MainDispatcher, #endif ActivatePriority, () => { lock (_gate) { Subscribe(); _isSubscribing = false; } }); } }
private static YogaSize MeasureText(ReactTextShadowNode textNode, YogaNode node, float width, YogaMeasureMode widthMode, float height, YogaMeasureMode 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 TextBlock { TextAlignment = TextAlignment.Left, TextWrapping = TextWrapping.Wrap, TextTrimming = TextTrimming.CharacterEllipsis, }; textNode.UpdateTextBlockCore(textBlock, true); for (var i = 0; i < textNode.ChildCount; ++i) { var child = textNode.GetChildAt(i); textBlock.Inlines.Add(ReactInlineShadowNodeVisitor.Apply(child)); } var normalizedWidth = YogaConstants.IsUndefined(width) ? double.PositiveInfinity : width; var normalizedHeight = YogaConstants.IsUndefined(height) ? double.PositiveInfinity : height; textBlock.Measure(new Size(normalizedWidth, normalizedHeight)); return(MeasureOutput.Make( (float)Math.Ceiling(textBlock.DesiredSize.Width), (float)Math.Ceiling(textBlock.DesiredSize.Height))); }); return(task.Result); }
public async Task UIManagerModule_Constants_ViewManagerOverrides() { await DispatcherHelpers.RunOnDispatcherAsync(ReactChoreographer.Initialize); var context = new ReactContext(); var viewManagers = new List <IViewManager> { new TestViewManager() }; var uiImplementationProvider = new UIImplementationProvider(); using (var actionQueue = new ActionQueue(ex => { })) { var module = await DispatcherHelpers.CallOnDispatcherAsync(() => new UIManagerModule(context, viewManagers, uiImplementationProvider, actionQueue)); var constants = module.Constants.GetMap("Test"); Assert.AreEqual(42, constants.GetMap("directEventTypes").GetValue("otherSelectionChange")); Assert.AreEqual(42, constants.GetMap("directEventTypes").GetMap("topSelectionChange").GetValue("registrationName")); Assert.AreEqual(42, constants.GetMap("directEventTypes").GetMap("topLoadingStart").GetValue("foo")); Assert.AreEqual(42, constants.GetMap("directEventTypes").GetValue("topLoadingError")); } await DispatcherHelpers.RunOnDispatcherAsync(ReactChoreographer.Dispose); }
public async Task ReactInstance_GetModules() { var module = new TestNativeModule(); var registry = new NativeModuleRegistry.Builder() .Add(module) .Build(); var executor = new MockJavaScriptExecutor { OnCallFunctionReturnFlushedQueue = (_, __, ___) => JValue.CreateNull(), OnFlushQueue = () => JValue.CreateNull(), OnInvokeCallbackAndReturnFlushedQueue = (_, __) => JValue.CreateNull() }; var builder = new ReactInstance.Builder() { QueueConfigurationSpec = ReactQueueConfigurationSpec.Default, Registry = registry, JavaScriptExecutorFactory = () => executor, BundleLoader = JavaScriptBundleLoader.CreateFileLoader("ms-appx:///Resources/test.js"), NativeModuleCallExceptionHandler = _ => { } }; var instance = await DispatcherHelpers.CallOnDispatcherAsync(() => builder.Build()); var actualModule = instance.GetNativeModule <TestNativeModule>(); Assert.AreSame(module, actualModule); var firstJSModule = instance.GetJavaScriptModule <TestJavaScriptModule>(); var secondJSModule = instance.GetJavaScriptModule <TestJavaScriptModule>(); Assert.AreSame(firstJSModule, secondJSModule); await DispatcherHelpers.CallOnDispatcherAsync(instance.DisposeAsync); }
public void fOpen(string fileUri, int mode, IPromise promise) { DispatcherHelpers.RunOnDispatcher(() => { Task.Run(() => { try { FileWrapper fileWrapper; FileStream fileStream; switch (mode) { case FILE_OPEN_MODE_READ: fileStream = new FileStream(fileUri, FileMode.Open, FileAccess.Read); fileWrapper = new FileWrapper(fileStream); break; case FILE_OPEN_MODE_WRITE: fileStream = new FileStream(fileUri, FileMode.Append, FileAccess.Write); fileWrapper = new FileWrapper(fileStream); break; default: throw new ArgumentException("Invalid open mode " + mode); } files.Add(fileWrapper.refId, fileWrapper); promise.Resolve(fileWrapper.refId); } catch (Exception e) { promise.Reject(ERROR_FATAL, e); } }); }); }
public async Task Timing_ManyTimers() { await DispatcherHelpers.RunOnDispatcherAsync(ReactChoreographer.Initialize); var count = 1000; var ids = new List <int>(count); var countdown = new CountdownEvent(count); var context = CreateReactContext(new MockInvocationHandler((name, args) => { Assert.AreEqual(name, nameof(JSTimers.callTimers)); var currentIds = (IList <int>)args[0]; ids.AddRange(currentIds); for (var i = 0; i < currentIds.Count; ++i) { countdown.Signal(); } })); var timing = new Timing(context); timing.Initialize(); await DispatcherHelpers.RunOnDispatcherAsync(context.OnResume); var now = DateTimeOffset.Now.ToUnixTimeMilliseconds(); for (var i = 0; i < count; ++i) { timing.createTimer(i, i, now, false); } Assert.IsTrue(countdown.Wait(count * 2)); await DispatcherHelpers.CallOnDispatcherAsync(context.DisposeAsync); await DispatcherHelpers.RunOnDispatcherAsync(ReactChoreographer.Dispose); }
/// <summary> /// Attach given <paramref name="rootView"/> to a React instance /// manager and start the JavaScript application using the JavaScript /// module provided by the <see cref="ReactRootView.JavaScriptModuleName"/>. If /// the React context is currently being (re-)created, or if the react /// context has not been created yet, the JavaScript application /// associated with the provided root view will be started /// asynchronously. This view will then be tracked by this manager and /// in case of React instance restart, it will be re-attached. /// </summary> /// <param name="rootView">The root view.</param> public void AttachMeasuredRootView(ReactRootView rootView) { Log.Info(ReactConstants.Tag, "[ReactInstanceManager] Enter AttachMeasuredRootView "); if (rootView == null) { throw new ArgumentNullException(nameof(rootView)); } DispatcherHelpers.AssertOnDispatcher(); _attachedRootViews.Add(rootView); // If the React context is being created in the background, the // JavaScript application will be started automatically when // creation completes, as root view is part of the attached root // view list. var currentReactContext = _currentReactContext; if (_contextInitializationTask == null && currentReactContext != null) { AttachMeasuredRootViewToInstance(rootView, currentReactContext.ReactInstance); } Log.Info(ReactConstants.Tag, "[ReactInstanceManager] Exit AttachMeasuredRootView "); }
/// <summary> /// Destroy the <see cref="ReactInstanceManager"/>. /// </summary> public async Task DisposeAsync() { DispatcherHelpers.AssertOnDispatcher(); // TODO: memory pressure hooks if (_useDeveloperSupport) { _devSupportManager.IsEnabled = false; } MoveToBeforeCreateLifecycleState(); var currentReactContext = _currentReactContext; if (currentReactContext != null) { await currentReactContext.DisposeAsync(); _currentReactContext = null; _hasStartedCreatingInitialContext = false; } ReactChoreographer.Dispose(); }
/// <summary> /// Creates a view with the given tag and class name. /// </summary> /// <param name="themedContext">The context.</param> /// <param name="tag">The tag.</param> /// <param name="className">The class name.</param> /// <param name="initialProperties">The properties.</param> public void CreateView(ThemedReactContext themedContext, int tag, string className, ReactStylesDiffMap initialProperties) { DispatcherHelpers.AssertOnDispatcher(); using (Tracer.Trace(Tracer.TRACE_TAG_REACT_VIEW, "NativeViewHierarcyManager.CreateView") .With("tag", tag) .With("className", className) .Start()) { var viewManager = _viewManagers.Get(className); var view = viewManager.CreateView(themedContext, _jsResponderHandler); _tagsToViews.Add(tag, view); _tagsToViewManagers.Add(tag, viewManager); // Uses an extension method and `Tag` property on // DependencyObject to store the tag of the view. view.SetTag(tag); view.SetReactContext(themedContext); if (initialProperties != null) { viewManager.UpdateProperties(view, initialProperties); } } }
private void DropView(DependencyObject view) { DispatcherHelpers.AssertOnDispatcher(); var tag = view.GetTag(); if (!_rootTags.ContainsKey(tag)) { // For non-root views, we notify the view manager with `OnDropViewInstance` var mgr = ResolveViewManager(tag); mgr.OnDropViewInstance(view.GetReactContext(), view); } var viewManager = default(IViewManager); if (_tagsToViewManagers.TryGetValue(tag, out viewManager)) { var viewParentManager = viewManager as IViewParentManager; if (viewParentManager != null) { for (var i = viewParentManager.GetChildCount(view) - 1; i >= 0; --i) { var child = viewParentManager.GetChildAt(view, i); var managedChild = default(DependencyObject); if (_tagsToViews.TryGetValue(child.GetTag(), out managedChild)) { DropView(managedChild); } } viewParentManager.RemoveAllChildren(view); } } _tagsToViews.Remove(tag); _tagsToViewManagers.Remove(tag); }
public void ScheduleError() { var ex = new Exception(); var id = Thread.CurrentThread.ManagedThreadId; var disp = DispatcherHelpers.EnsureDispatcher(); var evt = new ManualResetEvent(false); Exception thrownEx = null; disp.UnhandledException += (o, e) => { thrownEx = e.Exception; evt.Set(); e.Handled = true; }; var sch = new DispatcherScheduler(disp); sch.Schedule(() => { throw ex; }); evt.WaitOne(); disp.InvokeShutdown(); Assert.Same(ex, thrownEx); }
/// <summary> /// Updates the layout of a view. /// </summary> /// <param name="parentTag">The parent view tag.</param> /// <param name="tag">The view tag.</param> /// <param name="x">The left coordinate.</param> /// <param name="y">The top coordinate.</param> /// <param name="width">The layout width.</param> /// <param name="height">The layout height.</param> public void UpdateLayout(int parentTag, int tag, int x, int y, int width, int height) { DispatcherHelpers.AssertOnDispatcher(); using (Tracer.Trace(Tracer.TRACE_TAG_REACT_VIEW, "NativeViewHierarcyManager.UpdateLayout") .With("parentTag", parentTag) .With("tag", tag)) { var viewToUpdate = ResolveView(tag); var parentViewManager = default(IViewManager); var parentViewParentManager = default(IViewParentManager); if (!_tagsToViewManagers.TryGetValue(parentTag, out parentViewManager) || (parentViewParentManager = parentViewManager as IViewParentManager) == null) { throw new InvalidOperationException( $"Trying to use view with tag '{tag}' as a parent, but its manager doesn't extend ViewParentManager."); } if (!parentViewParentManager.NeedsCustomLayoutForChildren) { UpdateLayout(viewToUpdate, x, y, width, height); } } }
public void ShowDevOptionsDialog() { if (_devOptionsDialog != null || !IsEnabled) { return; } DispatcherHelpers.RunOnDispatcher(() => { var options = new[] { new DevOptionHandler( "Reload JavaScript", HandleReloadJavaScript), new DevOptionHandler( IsRemoteDebuggingEnabled ? "Stop JS Remote Debugging" : "Start JS Remote Debugging", () => { IsRemoteDebuggingEnabled = !IsRemoteDebuggingEnabled; HandleReloadJavaScript(); }), new DevOptionHandler( _devSettings.IsHotModuleReplacementEnabled ? "Disable Hot Reloading" : "Enable Hot Reloading", () => { _devSettings.IsHotModuleReplacementEnabled = !_devSettings.IsHotModuleReplacementEnabled; HandleReloadJavaScript(); }), new DevOptionHandler( _devSettings.IsReloadOnJavaScriptChangeEnabled ? "Disable Live Reload" : "Enable Live Reload", () => _devSettings.IsReloadOnJavaScriptChangeEnabled = !_devSettings.IsReloadOnJavaScriptChangeEnabled), new DevOptionHandler( _devSettings.IsElementInspectorEnabled ? "Hide Inspector" : "Show Inspector", () => { _devSettings.IsElementInspectorEnabled = !_devSettings.IsElementInspectorEnabled; _reactInstanceCommandsHandler.ToggleElementInspector(); }), }; _devOptionsDialogOpen = true; _devOptionsDialog = new DevOptionDialog(); _devOptionsDialog.Closed += (_, __) => { _devOptionsDialogOpen = false; _dismissDevOptionsDialog = null; _devOptionsDialog = null; }; foreach (var option in options) { _devOptionsDialog.Add(option.Name, option.OnSelect); } if (_redBoxDialog != null) { _dismissRedBoxDialog(); } #if WINDOWS_UWP var asyncInfo = _devOptionsDialog.ShowAsync(); _dismissDevOptionsDialog = asyncInfo.Cancel; foreach (var option in options) { option.HideDialog = _dismissDevOptionsDialog; } #else if (Application.Current != null && Application.Current.MainWindow != null && Application.Current.MainWindow.IsLoaded) { _devOptionsDialog.Owner = Application.Current.MainWindow; } else { _devOptionsDialog.Topmost = true; _devOptionsDialog.WindowStartupLocation = WindowStartupLocation.CenterScreen; } _dismissDevOptionsDialog = _devOptionsDialog.Close; _devOptionsDialog.Show(); foreach (var option in options) { option.HideDialog = _dismissDevOptionsDialog; } #endif }); }
private void ClearCallback() { DispatcherHelpers.AssertOnDispatcher(); CompositionTarget.Rendering -= ScheduleDispatch; }
public void ShowDevOptionsDialog() { if (_devOptionsDialog != null || !IsEnabled) { return; } Log.Info(ReactConstants.Tag, "Before build DevOptionHandler"); DispatcherHelpers.RunOnDispatcher(() => { var options = new[] { new DevOptionHandler( "Reload JavaScript", HandleReloadJavaScript), new DevOptionHandler( IsRemoteDebuggingEnabled ? "Stop JS Remote Debugging" : "Start JS Remote Debugging", () => { IsRemoteDebuggingEnabled = !IsRemoteDebuggingEnabled; HandleReloadJavaScript(); }), new DevOptionHandler( _devSettings.IsHotModuleReplacementEnabled ? "Disable Hot Reloading" : "Enable Hot Reloading", () => { _devSettings.IsHotModuleReplacementEnabled = !_devSettings.IsHotModuleReplacementEnabled; HandleReloadJavaScript(); }), new DevOptionHandler( _devSettings.IsReloadOnJavaScriptChangeEnabled ? "Disable Live Reload" : "Enable Live Reload", () => _devSettings.IsReloadOnJavaScriptChangeEnabled = !_devSettings.IsReloadOnJavaScriptChangeEnabled), new DevOptionHandler( _devSettings.IsElementInspectorEnabled ? "Hide Inspector" : "Show Inspector", () => { _devSettings.IsElementInspectorEnabled = !_devSettings.IsElementInspectorEnabled; _reactInstanceCommandsHandler.ToggleElementInspector(); }), new DevOptionHandler( "Set Host IP Address", () => { _devSettings.DebugServerHost = "109.123.120.200:8084"; _devServerHostDialog = DevServerHostDialog.GetInstance(); HideDevOptionsDialog(); _devServerHostDialog.Show(); _devServerHostDialog.ResetCloseEvent(); _devServerHostDialog.Closed += (dialog, __) => { var ipText = ((DevServerHostDialog)dialog).text; if (ipText.IndexOf(':') == -1) { ipText += ":8081"; } _devSettings.DebugServerHost = ipText; Log.Info(ReactConstants.Tag, "IP is " + ipText); }; }), }; _devOptionsDialogOpen = true; _devOptionsDialog = DevOptionDialog.GetInstance(); _devOptionsDialog.ResetCloseEvent(); _devOptionsDialog.Closed += (_, __) => { _devOptionsDialogOpen = false; _dismissDevOptionsDialog = null; _devOptionsDialog = null; }; foreach (var option in options) { _devOptionsDialog.Add(option); } if (_redBoxDialog != null) { _dismissRedBoxDialog(); } if (Application.Current != null && ReactProgram.RctWindow != null) { _devOptionsDialog.Owner = ReactProgram.RctWindow; } _dismissDevOptionsDialog = _devOptionsDialog.Close; _devOptionsDialog.Show(); foreach (var option in options) { option.HideDialog = _dismissDevOptionsDialog; } }); }
/// <summary> /// Used by the native animated module to bypass the process of /// updating the values through the shadow view hierarchy. This method /// will directly update the native views, which means that updates for /// layout-related properties won't be handled properly. /// </summary> /// <param name="tag">The view tag.</param> /// <param name="props">The properties</param> /// <remarks> /// Make sure you know what you're doing before calling this method :) /// </remarks> public void SynchronouslyUpdateViewOnDispatcherThread(int tag, ReactStylesDiffMap props) { DispatcherHelpers.AssertOnDispatcher(); _operationsQueue.NativeViewHierarchyManager.UpdateProperties(tag, props); }
/// <summary> /// Called before a <see cref="IReactInstance"/> is disposed. /// </summary> public override Task OnReactInstanceDisposeAsync() { DispatcherHelpers.RunOnDispatcher(_uiImplementation.OnDestroy); _eventDispatcher.OnReactInstanceDispose(); return(Task.CompletedTask); }
/// <summary> /// Called when the host is leaving background mode. /// </summary> public void OnLeavingBackground() { DispatcherHelpers.AssertOnDispatcher(); MoveToResumedLifecycleState(false); }
/// <summary> /// Called when the host entered background mode. /// </summary> public void OnEnteredBackground() { DispatcherHelpers.AssertOnDispatcher(); MoveToBackgroundLifecycleState(); }
/// <summary> /// Invokes the action to execute on dispatcher thread associated with view specified by <paramref name="tag" />. /// Action is not queued on operation queue, but it goes to dispatcher directly. /// </summary> /// <param name="tag">The react tag which specifies view.</param> /// <param name="action">The action to invoke.</param> public void InvokeAction(int tag, Action action) { DispatcherHelpers.RunOnDispatcher(GetQueueByTag(tag).Dispatcher, () => action()); }
internal void CleanupSafe() { // Inlining allowed DispatcherHelpers.RunOnDispatcher(this.Dispatcher, Cleanup, true); }
private void InvokeDefaultOnBackPressed() { DispatcherHelpers.AssertOnDispatcher(); _defaultBackButtonHandler?.Invoke(); }
/// <summary> /// Used by the native animated module to bypass the process of /// updating the values through the shadow view hierarchy. This method /// will directly update the native views, which means that updates for /// layout-related props won't be handled properly. /// </summary> /// <param name="tag">The view tag.</param> /// <param name="props">The props.</param> /// <remarks> /// Make sure you know what you're doing before calling this method :) /// </remarks> public bool SynchronouslyUpdateViewOnDispatcherThread(int tag, JObject props) { DispatcherHelpers.AssertOnDispatcher(); return(_operationsQueue.SynchronouslyUpdateViewOnDispatcherThread(tag, props)); }
/// <summary> /// Called when the host is leaving background mode. /// </summary> public void OnLeavingBackground() { DispatcherHelpers.AssertOnDispatcher(); _lifecycleStateMachine.OnLeavingBackground(); }
public void HandleException(Exception exception) { DispatcherHelpers.RunOnDispatcher(() => ExceptionDispatchInfo.Capture(exception).Throw()); }
private void DetachViewFromInstance(ReactRootView rootView, IReactInstance reactInstance) { DispatcherHelpers.AssertOnDispatcher(); reactInstance.GetJavaScriptModule <AppRegistry>().unmountApplicationComponentAtRootTag(rootView.GetTag()); }
/// <summary> /// Adds a root view to the hierarchy. /// </summary> /// <param name="tag">The root view tag.</param> /// <param name="rootView">The root view.</param> /// <param name="themedRootContext">The React context.</param> public void AddRootView( int tag, SizeMonitoringCanvas rootView, ThemedReactContext themedRootContext) { // This is called on layout manager thread // Extract dispatcher var rootViewDispatcher = rootView.Dispatcher; // _dispatcherToOperationQueueInfo contains a mapping of CoreDispatcher to <UIViewOperationQueueInstance, # of ReactRootView instances>. // Operation queue instances are created lazily whenever an "unknown" CoreDispatcher is detected. Each operation queue instance // works together with one dedicated NativeViewHierarchyManager and one ReactChoreographer. // One operation queue is the "main" one: // - is coupled with the CoreApplication.MainView dispatcher // - drives animations in ALL views if (!_dispatcherToOperationQueueInfo.TryGetValue(rootViewDispatcher, out var queueInfo)) { // Queue instance doesn't exist for this dispatcher, we need to create // Find the CoreApplicationView associated to the new CoreDispatcher CoreApplicationView foundView = CoreApplication.Views.First(v => v.Dispatcher == rootViewDispatcher); // Create new ReactChoreographer for this view/dispatcher. It will only be used for its DispatchUICallback services var reactChoreographer = ReactChoreographer.CreateSecondaryInstance(foundView); queueInfo = new QueueInstanceInfo() { queueInstance = new UIViewOperationQueueInstance( _reactContext, new NativeViewHierarchyManager(_viewManagerRegistry, rootViewDispatcher, OnViewsDropped), reactChoreographer), rootViewCount = 1 }; lock (_lock) { // Add new tuple to map _dispatcherToOperationQueueInfo.AddOrUpdate(rootViewDispatcher, queueInfo, (k, v) => throw new InvalidOperationException("Duplicate key")); if (_active) { // Simulate an OnResume from the correct dispatcher thread // (OnResume/OnSuspend/OnDestroy have this thread affinity, all other methods do enqueuings in a thread safe manner) // (No inlining here so we don't hold lock during call outs. Not a big deal since inlining // is only useful for main UI thread, and this code is not executed for that one) DispatcherHelpers.RunOnDispatcher(rootViewDispatcher, queueInfo.queueInstance.OnResume); } } } else { // Queue instance does exist. // Increment the count of root views. This is helpful for the case the count reaches 0 so we can cleanup the queue. queueInfo.rootViewCount++; } // Add tag _reactTagToOperationQueue.Add(tag, queueInfo.queueInstance); // Send forward queueInfo.queueInstance.AddRootView(tag, rootView, themedRootContext); }
/// <summary> /// Fetches the encoded BitmapImage. /// </summary> /// <param name="uri">The image uri.</param> /// <param name="token">The cancellation token.</param> /// <param name="dispatcher"> /// The current view's dispatcher, used to create BitmapImage. /// </param> /// <returns>The encoded BitmapImage.</returns> /// <exception cref="IOException"> /// If the image uri can't be found. /// </exception> public Task <BitmapImage> FetchEncodedBitmapImageAsync( Uri uri, CancellationToken token = default(CancellationToken), CoreDispatcher dispatcher = null) { var taskCompletionSource = new TaskCompletionSource <BitmapImage>(); var dataSource = FetchEncodedImage(ImageRequest.FromUri(uri), null); var dataSubscriber = new BaseDataSubscriberImpl <CloseableReference <IPooledByteBuffer> >( async response => { CloseableReference <IPooledByteBuffer> reference = response.GetResult(); if (reference != null) { //---------------------------------------------------------------------- // Phong Cao: InMemoryRandomAccessStream can't write anything < 16KB. // http://stackoverflow.com/questions/25928408/inmemoryrandomaccessstream-incorrect-behavior //---------------------------------------------------------------------- IPooledByteBuffer inputStream = reference.Get(); int supportedSize = Math.Max(16 * ByteConstants.KB, inputStream.Size); // Allocate temp buffer for stream convert byte[] bytesArray = default(byte[]); CloseableReference <byte[]> bytesArrayRef = default(CloseableReference <byte[]>); try { bytesArrayRef = _flexByteArrayPool.Get(supportedSize); bytesArray = bytesArrayRef.Get(); } catch (Exception) { // Allocates the byte array since the pool couldn't provide one bytesArray = new byte[supportedSize]; } try { inputStream.Read(0, bytesArray, 0, inputStream.Size); await DispatcherHelpers.CallOnDispatcherAsync(async() => { using (var outStream = new InMemoryRandomAccessStream()) using (var writeStream = outStream.AsStreamForWrite()) { await writeStream.WriteAsync(bytesArray, 0, supportedSize); outStream.Seek(0); BitmapImage bitmapImage = new BitmapImage(); await bitmapImage.SetSourceAsync(outStream).AsTask().ConfigureAwait(false); taskCompletionSource.SetResult(bitmapImage); } }, dispatcher).ConfigureAwait(false); } catch (Exception e) { taskCompletionSource.SetException(e); } finally { CloseableReference <IPooledByteBuffer> .CloseSafely(reference); CloseableReference <byte[]> .CloseSafely(bytesArrayRef); } } else { taskCompletionSource.SetResult(null); } }, response => { taskCompletionSource.SetException(response.GetFailureCause()); }); dataSource.Subscribe(dataSubscriber, _handleResultExecutor); token.Register(() => { dataSource.Close(); taskCompletionSource.TrySetCanceled(); }); return(taskCompletionSource.Task); }
/// <summary> /// Animation loop performs BFS over the graph of animated nodes. /// </summary> /// <remarks> /// We use incremented <see cref="_animatedGraphBFSColor"/> to mark /// nodes as visited in each of the BFS passes, which saves additional /// loops for clearing "visited" states. /// /// First BFS starts with nodes that are in <see cref="_updatedNodes" /> /// (this is, their value has been modified from JS in the last batch /// of JS operations) or directly attached to an active animation /// (thus linked to objects from <see cref="_activeAnimations"/>). In /// that step we calculate an attribute <see cref="AnimatedNode.ActiveIncomingNodes"/>. /// The second BFS runs in topological order over the sub-graph of /// *active* nodes. This is done by adding nodes to the BFS queue only /// if all its "predecessors" have already been visited. /// </remarks> /// <param name="renderingTime">Frame rendering time.</param> public void RunUpdates(TimeSpan renderingTime) { DispatcherHelpers.AssertOnDispatcher(); var activeNodesCount = 0; var updatedNodesCount = 0; var hasFinishedAnimations = false; // STEP 1. // BFS over graph of nodes starting from ones from `_updatedNodes` and ones that are attached to // active animations (from `mActiveAnimations)`. Update `ActiveIncomingNodes` property for each node // during that BFS. Store number of visited nodes in `activeNodesCount`. We "execute" active // animations as a part of this step. _animatedGraphBFSColor++; /* use new color */ var nodesQueue = new Queue <AnimatedNode>(); foreach (var node in _updatedNodes) { if (node.BfsColor != _animatedGraphBFSColor) { node.BfsColor = _animatedGraphBFSColor; activeNodesCount++; nodesQueue.Enqueue(node); } } foreach (var animation in _activeAnimations) { animation.RunAnimationStep(renderingTime); var valueNode = animation.AnimatedValue; if (valueNode.BfsColor != _animatedGraphBFSColor) { valueNode.BfsColor = _animatedGraphBFSColor; activeNodesCount++; nodesQueue.Enqueue(valueNode); } if (animation.HasFinished) { hasFinishedAnimations = true; } } while (nodesQueue.Count > 0) { var nextNode = nodesQueue.Dequeue(); if (nextNode.Children != null) { foreach (var child in nextNode.Children) { child.ActiveIncomingNodes++; if (child.BfsColor != _animatedGraphBFSColor) { child.BfsColor = _animatedGraphBFSColor; activeNodesCount++; nodesQueue.Enqueue(child); } } } } // STEP 2 // BFS over the graph of active nodes in topological order -> visit node only when all its // "predecessors" in the graph have already been visited. It is important to visit nodes in that // order as they may often use values of their predecessors in order to calculate "next state" // of their own. We start by determining the starting set of nodes by looking for nodes with // `ActiveIncomingNodes = 0` (those can only be the ones that we start BFS in the previous // step). We store number of visited nodes in this step in `updatedNodesCount` _animatedGraphBFSColor++; // find nodes with zero "incoming nodes", those can be either nodes from `mUpdatedNodes` or // ones connected to active animations foreach (var node in _updatedNodes) { if (node.ActiveIncomingNodes == 0 && node.BfsColor != _animatedGraphBFSColor) { node.BfsColor = _animatedGraphBFSColor; updatedNodesCount++; nodesQueue.Enqueue(node); } } foreach (var animation in _activeAnimations) { var valueNode = animation.AnimatedValue; if (valueNode.ActiveIncomingNodes == 0 && valueNode.BfsColor != _animatedGraphBFSColor) { valueNode.BfsColor = _animatedGraphBFSColor; updatedNodesCount++; nodesQueue.Enqueue(valueNode); } } // Run main "update" loop while (nodesQueue.Count > 0) { var nextNode = nodesQueue.Dequeue(); nextNode.Update(); var propsNode = nextNode as PropsAnimatedNode; var valueNode = default(ValueAnimatedNode); if (propsNode != null) { propsNode.UpdateView(_uiImplementation); } else if ((valueNode = nextNode as ValueAnimatedNode) != null) { // Potentially send events to JS when the node's value is updated valueNode.OnValueUpdate(); } if (nextNode.Children != null) { foreach (var child in nextNode.Children) { child.ActiveIncomingNodes--; if (child.BfsColor != _animatedGraphBFSColor && child.ActiveIncomingNodes == 0) { child.BfsColor = _animatedGraphBFSColor; updatedNodesCount++; nodesQueue.Enqueue(child); } } } } // Verify that we've visited *all* active nodes. Throw otherwise as this would mean there is a // cycle in animated node graph. We also take advantage of the fact that all active nodes are // visited in the step above so that all the nodes properties `mActiveIncomingNodes` are set to // zero if (activeNodesCount != updatedNodesCount) { throw new InvalidOperationException( Invariant($"Looks like animated nodes graph has cycles, there are {activeNodesCount} but visited only {updatedNodesCount}.")); } // Clean _updatedNodes queue _updatedNodes.Clear(); // Cleanup finished animations. Iterate over the array of animations and override ones that has // finished, then resize `_activeAnimations`. if (hasFinishedAnimations) { int dest = 0; for (var i = 0; i < _activeAnimations.Count; ++i) { var animation = _activeAnimations[i]; if (!animation.HasFinished) { _activeAnimations[dest++] = animation; } else { animation.EndCallback.Invoke(new JObject { { "finished", true }, }); } } for (var i = _activeAnimations.Count - 1; i >= dest; --i) { _activeAnimations.RemoveAt(i); } } }