/// <summary> /// Updates the rendered state of the specified <see cref="IComponent"/>. /// </summary> /// <param name="componentId">The identifier of the <see cref="IComponent"/> to render.</param> protected internal void RenderNewBatch(int componentId) { // It's very important that components' rendering logic has no side-effects, and in particular // components must *not* trigger Render from inside their render logic, otherwise you could // easily get hard-to-debug infinite loops. // Since rendering is currently synchronous and single-threaded, we can enforce the above by // checking here that no other rendering process is already underway. This also means we only // need a single _renderBatchBuilder instance that can be reused throughout the lifetime of // the Renderer instance, which also means we're not allocating on a typical render cycle. // In the future, if rendering becomes async, we'll need a more sophisticated system of // capturing successive diffs from each component and probably serializing them for the // interop calls instead of using shared memory. // Note that Monitor.TryEnter is not yet supported in Mono WASM, so using the following instead var renderAlreadyRunning = Interlocked.CompareExchange(ref _renderBatchLock, 1, 0) == 1; if (renderAlreadyRunning) { throw new InvalidOperationException("Cannot render while a render is already in progress. " + "Render logic must not have side-effects such as manually triggering other rendering."); } try { RenderInExistingBatch(_sharedRenderBatchBuilder, componentId); UpdateDisplay(_sharedRenderBatchBuilder.ToBatch()); } finally { _sharedRenderBatchBuilder.Clear(); Interlocked.Exchange(ref _renderBatchLock, 0); } }
private void ProcessRenderQueue() { _isBatchInProgress = true; try { // Process render queue until empty while (_batchBuilder.ComponentRenderQueue.Count > 0) { var nextToRender = _batchBuilder.ComponentRenderQueue.Dequeue(); RenderInExistingBatch(nextToRender); } UpdateDisplay(_batchBuilder.ToBatch()); } finally { RemoveEventHandlerIds(_batchBuilder.DisposedEventHandlerIds.ToRange()); _batchBuilder.Clear(); _isBatchInProgress = false; } }