Skip to content

Commit

Permalink
Component prerequisites (#5)
Browse files Browse the repository at this point in the history
* Component prerequisites
  • Loading branch information
YuriyDurov authored Sep 19, 2024
1 parent 465a992 commit 80e48ed
Show file tree
Hide file tree
Showing 11 changed files with 719 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<p>Current rendermode: @RendererInfo.Name</p>

State: @Text <br/>
State: @_stateText <br/>

Descendant components ready: @_descendantsInitializedCount <br/>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,40 @@ namespace BitzArt.Blazor.State.SampleApp.Client.Pages;

public partial class CounterPage : PersistentComponentBase
{
[ComponentState]
private string Text = "Not Initialized";
private readonly ComponentPrerequisiteCallback _callback = new();

public CounterPage()
{
// this prerequisite needs to be cancelled manually via the callback
Prerequisites.AddManual(() => _descendantsInitializedCount > 0, _callback, true);

// this prerequisite will automatically check for completion every 100ms
Prerequisites.AddAuto(100, () => _descendantsInitializedCount > 0, true);
}

[ComponentState]
private int _descendantsInitializedCount = 0;
private string _stateText = "Not Initialized";

[ComponentState]
private string _prerequisitesText = "Waiting for prerequisites...";

protected override async Task EnsurePrerequisitesAsync()
[ComponentState]
private int _descendantsInitializedCount = 0;

protected override void Initialize()
{
while (_descendantsInitializedCount < 1) await Task.Delay(100);
_prerequisitesText = "All prerequisites are met";
}

protected override void InitializeState()
{
Text = "Initialized";
_stateText = "Initialized";
}

private void OnDescendantInitialized()
{
_descendantsInitializedCount++;

if (_descendantsInitializedCount > 0) _callback.Invoke();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>BitzArt.Blazor.Render</RootNamespace>
<RootNamespace>BitzArt.Blazor.Render.Strategies</RootNamespace>
<GenerateDocumentationFile>true</GenerateDocumentationFile>

<PackageId>BitzArt.Blazor.RenderStrategies</PackageId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;

namespace BitzArt.Blazor.State;
namespace BitzArt.Blazor;

/// <summary>
/// Represents a component that uses a <see cref="ComponentRenderStrategy"/> to render.
/// </summary>
public abstract class StrategyRenderedComponent : IComponent, IHandleAfterRender, IHandleEvent
{
/// <summary>
/// A collection of prerequisites that need to be met before the component lifecycle starts.
/// </summary>
protected internal ComponentPrerequisiteCollection Prerequisites { get; } = new();

/// <summary>
/// Indicates whether the component should wait for complete initialization
/// before proceeding with further rendering and initializing descendant components. <br/>
Expand Down Expand Up @@ -112,22 +117,33 @@ internal bool ShouldRenderInternal()
//protected ResourceAssetCollection Assets => RenderStrategy!.Handle.Assets;

/// <summary>
/// This method can be overridden to perform an action before the component lifecycle starts,
/// such as waiting for prerequisites to be met. <br/>
/// <p>
/// ⚠️ Temporary API, will be removed and replaced with the Prerequisites API,
/// which will ensure the prerequisites are met based on a set of defined conditions
/// that need to be met before the component lifecycle starts.
/// </p>
/// Method invoked when the component is ready to start, having received its
/// initial parameters from its parent in the render tree.
/// </summary>
/// <returns></returns>
protected internal virtual Task EnsurePrerequisitesAsync()
=> Task.CompletedTask;
protected virtual void Initialize()
{
}

internal void InitializeInternal()
=> Initialize();

/// <summary>
/// Method invoked when the component is ready to start, having received its
/// initial parameters from its parent in the render tree.
/// </summary>
protected virtual Task InitializeAsync()
=> Task.CompletedTask;

internal Task InitializeAsyncInternal()
=> InitializeAsync();

/// <summary>
/// Method invoked after the component has initialized.
/// By default, this method still running will not prevent the component from rendering. <br/>
/// To perform a synchronous action that should be completed before proceeding
/// to rendering the component and it's descendants,
/// use <see cref="Initialize">Initialize</see>
/// </summary>
protected virtual void OnInitialized()
{
}
Expand All @@ -136,13 +152,13 @@ internal void OnInitializedInternal()
=> OnInitialized();

/// <summary>
/// Method invoked when the component is ready to start, having received its
/// initial parameters from its parent in the render tree.
///
/// Override this method if you will perform an asynchronous operation and
/// want the component to refresh when that operation is completed.
/// Method invoked after the component has initialized.
/// By default, this method still running will not prevent the component from rendering. <br/>
/// To perform an asynchronous action that should be completed before proceeding
/// to rendering the component and it's descendants,
/// use <see cref="InitializeAsync">InitializeAsync</see>
/// </summary>
/// <returns>A <see cref="Task"/> representing any asynchronous operation.</returns>
/// <returns>A <see cref="Task"/> representing the operation.</returns>
protected virtual Task OnInitializedAsync()
=> Task.CompletedTask;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
using System.Diagnostics.CodeAnalysis;
using System.Threading;

namespace BitzArt.Blazor;

/// <summary>
/// A component prerequisite that automatically checks for completion at a specified interval.<br/>
/// The <see cref="ComponentPrerequisite">manual prerequisites</see> are more performant,
/// so use this type of prerequisite only when periodic checks are required and/or application performance is not a concern.
/// </summary>
public class AutomaticComponentPrerequisite : ComponentPrerequisite
{
/// <summary>
/// Interval between each check, in milliseconds.
/// </summary>
public int Period { get; set; }

/// <summary>
/// <inheritdoc cref="ComponentPrerequisite(Func{bool},Func{bool})"/>
/// </summary>
/// <param name="period"><inheritdoc cref="Period" path="/summary"/></param>
/// <param name="requirement"><inheritdoc cref="ComponentPrerequisite.Requirement" path="/summary"/></param>/>
/// <param name="constraint"><inheritdoc cref="ComponentPrerequisite.Constraint" path="/summary"/></param>
/// <param name="callback"><inheritdoc cref="ComponentPrerequisite.Callback" path="/summary"/></param>
/// <param name="allowComponentInitialization"><inheritdoc cref="ComponentPrerequisite.AllowComponentInitialization" path="/summary"/></param>
public AutomaticComponentPrerequisite(int period, Func<bool> requirement, Func<bool> constraint, ComponentPrerequisiteCallback callback, bool allowComponentInitialization)
: this(period, requirement, constraint, callback)
{
AllowComponentInitialization = allowComponentInitialization;
}

/// <summary>
/// <inheritdoc cref="ComponentPrerequisite(Func{bool},Func{bool})"/>
/// </summary>
/// <param name="period"><inheritdoc cref="Period" path="/summary"/></param>
/// <param name="requirement"><inheritdoc cref="ComponentPrerequisite.Requirement" path="/summary"/></param>/>
/// <param name="constraint"><inheritdoc cref="ComponentPrerequisite.Constraint" path="/summary"/></param>
/// <param name="callback"><inheritdoc cref="ComponentPrerequisite.Callback" path="/summary"/></param>
public AutomaticComponentPrerequisite(int period, Func<bool> requirement, Func<bool> constraint, ComponentPrerequisiteCallback callback)
: this(period, requirement, constraint)
{
Callback = callback;
callback.Attach(this);
}

/// <summary>
/// <inheritdoc cref="ComponentPrerequisite(Func{bool},Func{bool})"/>
/// </summary>
/// <param name="period"><inheritdoc cref="Period" path="/summary"/></param>
/// <param name="requirement"><inheritdoc cref="ComponentPrerequisite.Requirement" path="/summary"/></param>
/// <param name="constraint"><inheritdoc cref="ComponentPrerequisite.Constraint" path="/summary"/></param>
/// <param name="allowComponentInitialization"><inheritdoc cref="ComponentPrerequisite.AllowComponentInitialization" path="/summary"/></param>
public AutomaticComponentPrerequisite(int period, Func<bool> requirement, Func<bool> constraint, bool allowComponentInitialization)
: this(period, requirement, constraint)
{
AllowComponentInitialization = allowComponentInitialization;
}

/// <summary>
/// <inheritdoc cref="ComponentPrerequisite(Func{bool})"/>
/// </summary>
/// <param name="period"><inheritdoc cref="Period" path="/summary"/></param>
/// <param name="requirement"><inheritdoc cref="ComponentPrerequisite.Requirement" path="/summary"/></param>
/// <param name="callback"><inheritdoc cref="ComponentPrerequisite.Callback" path="/summary"/></param>
/// <param name="allowComponentInitialization"><inheritdoc cref="ComponentPrerequisite.AllowComponentInitialization" path="/summary"/></param>
public AutomaticComponentPrerequisite(int period, Func<bool> requirement, ComponentPrerequisiteCallback callback, bool allowComponentInitialization)
: this(period, requirement, allowComponentInitialization)
{
Callback = callback;
callback.Attach(this);
}

/// <summary>
/// <inheritdoc cref="ComponentPrerequisite(Func{bool})"/>
/// </summary>
/// <param name="period"><inheritdoc cref="Period" path="/summary"/></param>
/// <param name="requirement"><inheritdoc cref="ComponentPrerequisite.Requirement" path="/summary"/></param>
/// <param name="allowComponentInitialization"><inheritdoc cref="ComponentPrerequisite.AllowComponentInitialization" path="/summary"/></param>
public AutomaticComponentPrerequisite(int period, Func<bool> requirement, bool allowComponentInitialization)
: this(period, requirement)
{
AllowComponentInitialization = allowComponentInitialization;
}

/// <summary>
/// <inheritdoc cref="ComponentPrerequisite(Func{bool})"/>
/// </summary>
/// <param name="period"><inheritdoc cref="Period" path="/summary"/></param>
/// <param name="requirement"><inheritdoc cref="ComponentPrerequisite.Requirement" path="/summary"/></param>
/// <param name="constraint"><inheritdoc cref="ComponentPrerequisite.Constraint" path="/summary"/></param>
public AutomaticComponentPrerequisite(int period, Func<bool> requirement, Func<bool> constraint)
:this(period, requirement)
{
Constraint = constraint;
}

/// <summary>
/// Creates a new instance of <see cref="ComponentPrerequisite"/>.
/// </summary>
/// <param name="period"><inheritdoc cref="Period" path="/summary"/></param>
/// <param name="requirement"><inheritdoc cref="ComponentPrerequisite.Requirement" path="/summary"/></param>
[SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "In order to have a distinct comment for this constructor")]
public AutomaticComponentPrerequisite(int period, Func<bool> requirement)
:base(requirement)
{
Period = period;
}

private protected override async Task WaitAsync(Task timeoutTask)
{
while (!timeoutTask.IsCompleted && !Requirement.Invoke())
{
if (timeoutTask.IsCompleted) break;
var periodTask = Task.Delay(Period, _cancellationToken!.Value);
await Task.WhenAny(timeoutTask, periodTask);
}
}
}
Loading

0 comments on commit 80e48ed

Please sign in to comment.