Skip to content

Commit

Permalink
3.0.0-preview4, batch rendering, async, server-side (#24)
Browse files Browse the repository at this point in the history
* Implement call buffer, async

* Initialize semaphore

* Server-side batch rendering, test

* README changes

* Update CI build to use .NET Core 3.0 preview

* Update README

* Updated to v3.0.0-preview4

* Remove references to myget

* Fix YAML

* Update .NET Core preview version in CI build

* Fixed SDK YAML tasks
  • Loading branch information
WilStead authored and galvesribeiro committed Apr 30, 2019
1 parent b356c11 commit f9d1bc3
Show file tree
Hide file tree
Showing 49 changed files with 1,306 additions and 374 deletions.
11 changes: 7 additions & 4 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ dotnet_naming_style.first_upper.capitalization = first_word_upper
# prefix_interface_interface_with_i - Interfaces must be PascalCase and the first character of an interface must be an 'I'
dotnet_naming_style.prefix_interface_interface_with_i.capitalization = pascal_case
dotnet_naming_style.prefix_interface_interface_with_i.required_prefix = I
# underscore_camel_case - Define the _camelCase style
dotnet_naming_style.underscore_camel_case.capitalization = camel_case
dotnet_naming_style.underscore_camel_case.required_prefix = _

# Naming Rules
# Constant fields must be PascalCase
Expand All @@ -109,18 +112,18 @@ dotnet_naming_rule.non_private_readonly_fields_must_be_pascal_case.style = pasca
dotnet_naming_rule.static_readonly_fields_must_be_pascal_case.severity = warning
dotnet_naming_rule.static_readonly_fields_must_be_pascal_case.symbols = static_readonly_fields
dotnet_naming_rule.static_readonly_fields_must_be_pascal_case.style = pascal_case
# Private readonly fields must be camelCase
# Private readonly fields must be _camelCase
dotnet_naming_rule.private_readonly_fields_must_be_camel_case.severity = warning
dotnet_naming_rule.private_readonly_fields_must_be_camel_case.symbols = private_readonly_fields
dotnet_naming_rule.private_readonly_fields_must_be_camel_case.style = camel_case
dotnet_naming_rule.private_readonly_fields_must_be_camel_case.style = underscore_camel_case
# Public and internal fields must be PascalCase
dotnet_naming_rule.public_internal_fields_must_be_pascal_case.severity = warning
dotnet_naming_rule.public_internal_fields_must_be_pascal_case.symbols = public_internal_fields
dotnet_naming_rule.public_internal_fields_must_be_pascal_case.style = pascal_case
# Private and protected fields must be camelCase
# Private and protected fields must be _camelCase
dotnet_naming_rule.private_protected_fields_must_be_camel_case.severity = warning
dotnet_naming_rule.private_protected_fields_must_be_camel_case.symbols = private_protected_fields
dotnet_naming_rule.private_protected_fields_must_be_camel_case.style = camel_case
dotnet_naming_rule.private_protected_fields_must_be_camel_case.style = underscore_camel_case
# Public members must be capitalized
dotnet_naming_rule.public_members_must_be_capitalized.severity = warning
dotnet_naming_rule.public_members_must_be_capitalized.symbols = public_symbols
Expand Down
4 changes: 4 additions & 0 deletions .vsts-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ variables:
buildConfiguration: 'Release'

steps:
- task: DotNetCoreInstaller@0
inputs:
packageType: 'sdk'
version: '3.0.100-preview4-011223'
- task: Npm@1
inputs:
command: 'install'
Expand Down
5 changes: 5 additions & 0 deletions .vsts-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ variables:
buildConfiguration: 'Release'

steps:
- task: DotNetCoreInstaller@0
inputs:
packageType: 'sdk'
version: '3.0.100-preview4-011223'

- task: Npm@1
inputs:
command: 'install'
Expand Down
21 changes: 18 additions & 3 deletions Canvas.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26124.0
# Visual Studio Version 16
VisualStudioVersion = 16.0.28711.60
MinimumVisualStudioVersion = 15.0.26124.0
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{B286BCBD-DAD8-4DE7-9334-3DE18DF233AF}"
EndProject
Expand All @@ -13,12 +13,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
README.md = README.md
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blazor.Extensions.Canvas.Test", "test\Blazor.Extensions.Canvas.Test\Blazor.Extensions.Canvas.Test.csproj", "{C4BB6A39-28E6-454D-8679-92562CEAD0A9}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blazor.Extensions.Canvas.Test.ClientSide", "test\Blazor.Extensions.Canvas.Test.ClientSide\Blazor.Extensions.Canvas.Test.ClientSide.csproj", "{C4BB6A39-28E6-454D-8679-92562CEAD0A9}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blazor.Extensions.Canvas.JS", "src\Blazor.Extensions.Canvas.JS\Blazor.Extensions.Canvas.JS.csproj", "{1C49147F-7C73-4962-A71C-6A193970D058}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blazor.Extensions.Canvas", "src\Blazor.Extensions.Canvas\Blazor.Extensions.Canvas.csproj", "{CB6A1BDA-7768-4A0C-A802-D3AE0C19C120}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blazor.Extensions.Canvas.Test.ServerSide", "test\Blazor.Extensions.Canvas.Test.ServerSide\Blazor.Extensions.Canvas.Test.ServerSide.csproj", "{D2242105-73D1-4F44-9A80-13D51B6B35FE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -65,6 +67,18 @@ Global
{CB6A1BDA-7768-4A0C-A802-D3AE0C19C120}.Release|x64.Build.0 = Release|Any CPU
{CB6A1BDA-7768-4A0C-A802-D3AE0C19C120}.Release|x86.ActiveCfg = Release|Any CPU
{CB6A1BDA-7768-4A0C-A802-D3AE0C19C120}.Release|x86.Build.0 = Release|Any CPU
{D2242105-73D1-4F44-9A80-13D51B6B35FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D2242105-73D1-4F44-9A80-13D51B6B35FE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D2242105-73D1-4F44-9A80-13D51B6B35FE}.Debug|x64.ActiveCfg = Debug|Any CPU
{D2242105-73D1-4F44-9A80-13D51B6B35FE}.Debug|x64.Build.0 = Debug|Any CPU
{D2242105-73D1-4F44-9A80-13D51B6B35FE}.Debug|x86.ActiveCfg = Debug|Any CPU
{D2242105-73D1-4F44-9A80-13D51B6B35FE}.Debug|x86.Build.0 = Debug|Any CPU
{D2242105-73D1-4F44-9A80-13D51B6B35FE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D2242105-73D1-4F44-9A80-13D51B6B35FE}.Release|Any CPU.Build.0 = Release|Any CPU
{D2242105-73D1-4F44-9A80-13D51B6B35FE}.Release|x64.ActiveCfg = Release|Any CPU
{D2242105-73D1-4F44-9A80-13D51B6B35FE}.Release|x64.Build.0 = Release|Any CPU
{D2242105-73D1-4F44-9A80-13D51B6B35FE}.Release|x86.ActiveCfg = Release|Any CPU
{D2242105-73D1-4F44-9A80-13D51B6B35FE}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -73,6 +87,7 @@ Global
{C4BB6A39-28E6-454D-8679-92562CEAD0A9} = {20DAA632-F8AD-4C5F-9E5F-FC82B7CB56A7}
{1C49147F-7C73-4962-A71C-6A193970D058} = {B286BCBD-DAD8-4DE7-9334-3DE18DF233AF}
{CB6A1BDA-7768-4A0C-A802-D3AE0C19C120} = {B286BCBD-DAD8-4DE7-9334-3DE18DF233AF}
{D2242105-73D1-4F44-9A80-13D51B6B35FE} = {20DAA632-F8AD-4C5F-9E5F-FC82B7CB56A7}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A97C0A4B-E309-4485-BB76-898B37BFBFFF}
Expand Down
80 changes: 64 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ Blazor Extensions are a set of packages with the goal of adding useful things to

This package wraps [HTML5 Canvas](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas) APIs.

> **NOTE**: Only Canvas 2d is supported. WebGL will come later (contributions are welcome!).
Both Canvas 2D and WebGL are supported.

Both client and server-side scenarios using either Blazor or Razor Components are supported.

**NOTE** Currently targets the v3.0.0-preview4 version of Blazor/Razor Components, which has a limitation regarding static files included in component libraries (aspnet/AspNetCore#6349). As a temporary workaround, manually add the `blazor.extensions.canvas.js` file in a `<script>` tag in the `<head>` element of your project website.

# Installation

Expand All @@ -26,8 +30,6 @@ Install-Package Blazor.Extensions.Canvas

## Usage

The following snippet shows how to consume the Canvas API in a Blazor component.

On your `_ViewImports.cshtml` add the `using` and TagHelper entries:

```c#
Expand All @@ -38,33 +40,79 @@ On your `_ViewImports.cshtml` add the `using` and TagHelper entries:
On your .cshtml add a `BECanvas` and make sure you set the `ref` to a field on your component:

```c#
@page "/"
@inherits IndexComponent

<h1>Canvas demo!!!</h1>

<BECanvas ref="@_canvasReference"></BECanvas>
```

On your component C# code (regardless if inline on .cshtml or in a .cs file), from a `BECanvasComponent` reference, create a `Canvas2dContext`, and then use the context methods to draw on the canvas:
### 2D

On your component C# code (regardless if inline on .razor or in a .cs file), from a `BECanvasComponent` reference, create a `Canvas2DContext`, and then use the context methods to draw on the canvas:

```c#
private Canvas2dContext _context;
private Canvas2DContext _context;

protected BECanvasComponent _canvasReference;

protected override void OnAfterRender()
protected override async Task OnAfterRenderAsync()
{
this._context = this._canvasReference.CreateCanvas2d();
this._context.FillStyle = "green";
this._context = await this._canvasReference.CreateCanvas2DAsync();
await this._context.SetFillStyleAsync("green");

await this._context.FillRectAsync(10, 100, 100, 100);

await this._context.SetFontAsync("48px serif");
await this._context.StrokeTextAsync("Hello Blazor!!!", 10, 100);
}
```

**NOTE** You cannot call `CreateCanvas2DAsync` in `OnInitAsync`, because the underlying `<canvas>` element is not yet present in the generated markup.

### WebGL

this._context.FillRect(10, 100, 100, 100);
On your component C# code (regardless if inline on .razor or in a .cs file), from a `BECanvasComponent` reference, create a `WebGLContext`, and then use the context methods to draw on the canvas:

```c#
private WebGLContext _context;

protected BECanvasComponent _canvasReference;

this._context.Font = "48px serif";
this._context.StrokeText("Hello Blazor!!!", 10, 100);
protected override async Task OnAfterRenderAsync()
{
this._context = await this._canvasReference.CreateWebGLAsync();

await this._context.ClearColorAsync(0, 0, 0, 1);
await this._context.ClearAsync(BufferBits.COLOR_BUFFER_BIT);
}
```

**NOTE** You cannot call `CreateWebGLAsync` in `OnInitAsync`, because the underlying `<canvas>` element is not yet present in the generated markup.

### Call Batching

All javascript interop are batched as needed to improve performance. In high-performance scenarios this behavior will not have any effect: each call will execute immediately. In low-performance scenarios, consective calls to canvas APIs will be queued. JavaScript interop calls will be made with each batch of queued commands sequentially, to avoid the performance impact of multiple concurrent interop calls.

When using server-side Razor Components, because of the server-side rendering mechanism, only the last drawing operation executed will appear to render on the client, overwriting all previous operations. In the example code above, for example, drawing the triangles would appear to "erase" the black background drawn immediately before, leaving the canvas transparent.

To avoid this issue, all WebGL **drawing** operations should be explicitly preceded and followed by `BeginBatchAsync` and `EndBatchAsync` calls.

For example:

```c#
await this._context.ClearColorAsync(0, 0, 0, 1); // this call does not draw anything, so it does not need to be included in the explicit batch
await this._context.BeginBatchAsync(); // begin the explicit batch
await this._context.ClearAsync(BufferBits.COLOR_BUFFER_BIT);
await this._context.DrawArraysAsync(Primitive.TRIANGLES, 0, 3);

await this._context.EndBatchAsync(); // execute all currently batched calls
```

It is best to structure your code so that `BeginBatchAsync` and `EndBatchAsync` surround as few calls as possible. That will allow the automatic batching behavior to send calls in the most efficient manner possible, and avoid unnecessary performance impacts.

Methods which return values are never batched. Such methods may be called at any time, *even after calling `BeginBatchAsync`*, without interrupting the batching of other calls.

***NOTE*** The "overwriting" behavior of server-side code is unpredictable, and shouldn't be relied on as a feature. In low-performance situations calls can be batched automatically, even when you don't explicitly use `BeginBatchAsync` and `EndBatchAsync`.

# Contributions and feedback

Please feel free to use the component, open issues, fix bugs or provide feedback.
Expand Down
2 changes: 2 additions & 0 deletions blazor.extensions.canvas.js

Large diffs are not rendered by default.

15 changes: 13 additions & 2 deletions src/Blazor.Extensions.Canvas.JS/Blazor.Extensions.Canvas.JS.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Library</OutputType>
Expand All @@ -14,7 +14,6 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Blazor.Build" Version="0.7.0" />
<WebpackInputs Include="**\*.ts" Exclude="dist\**;node_modules\**" />
</ItemGroup>

Expand All @@ -30,4 +29,16 @@
<EmbeddedResource Include="dist\**\*.js" LogicalName="blazor:js:%(RecursiveDir)%(Filename)%(Extension)" />
</ItemGroup>
</Target>

<ItemGroup>
<WebpackOutputs Include="dist\**\*.js" />
</ItemGroup>

<Target Name="CopyDist" AfterTargets="RunWebpack" DependsOnTargets="RunWebpack">
<Copy SourceFiles="@(WebpackOutputs)" DestinationFolder="..\..\test\Blazor.Extensions.Canvas.Test.ServerSide\wwwroot\dist" SkipUnchangedFiles="true" />
</Target>

<Target Name="CopyDistForServerSideTest" AfterTargets="RunWebpack" DependsOnTargets="RunWebpack">
<Copy SourceFiles="@(WebpackOutputs)" DestinationFolder="..\.." SkipUnchangedFiles="true" />
</Target>
</Project>
25 changes: 24 additions & 1 deletion src/Blazor.Extensions.Canvas.JS/src/CanvasContextManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export class ContextManager {

public setProperty = (canvas: HTMLCanvasElement, property: string, value: any) => {
const context = this.getContext(canvas);
context[property] = this.deserialize(property, value);
this.setPropertyWithContext(context, property, value);
}

public getProperty = (canvas: HTMLCanvasElement, property: string) => {
Expand All @@ -50,9 +50,32 @@ export class ContextManager {

public call = (canvas: HTMLCanvasElement, method: string, args: any) => {
const context = this.getContext(canvas);
return this.callWithContext(context, method, args);
}

public callBatch = (canvas: HTMLCanvasElement, batchedCalls: any[][]) => {
const context = this.getContext(canvas);
for (let i = 0; i < batchedCalls.length; i++) {
let params = batchedCalls[i].slice(2);
if (batchedCalls[i][1]) {
this.callWithContext(context, batchedCalls[i][0], params);
} else {
this.setPropertyWithContext(
context,
batchedCalls[i][0],
Array.isArray(params) && params.length > 0 ? params[0] : null);
}
}
}

private callWithContext = (context: any, method: string, args: any) => {
return this.serialize(this.prototypes[method].apply(context, args != undefined ? args.map((value) => this.deserialize(method, value)) : []));
}

private setPropertyWithContext = (context: any, property: string, value: any) => {
context[property] = this.deserialize(property, value);
}

private getContext = (canvas: HTMLCanvasElement) => {
if (!canvas) throw new Error('Invalid canvas.');

Expand Down
3 changes: 0 additions & 3 deletions src/Blazor.Extensions.Canvas/BECanvas.cshtml

This file was deleted.

3 changes: 3 additions & 0 deletions src/Blazor.Extensions.Canvas/BECanvas.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@inherits BECanvasComponent

<canvas id="@Id" width="@Width" height="@Height" ref="_canvasRef"></canvas>
13 changes: 8 additions & 5 deletions src/Blazor.Extensions.Canvas/BECanvasComponent.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
using Microsoft.AspNetCore.Blazor;
using Microsoft.AspNetCore.Blazor.Components;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using System;

namespace Blazor.Extensions
{
public class BECanvasComponent : BlazorComponent
public class BECanvasComponent : ComponentBase
{
[Parameter]
protected long Height { get; set; }
Expand All @@ -13,8 +13,11 @@ public class BECanvasComponent : BlazorComponent
protected long Width { get; set; }

protected readonly string Id = Guid.NewGuid().ToString();
protected ElementRef canvasRef;
protected ElementRef _canvasRef;

internal ElementRef CanvasReference => this.canvasRef;
internal ElementRef CanvasReference => this._canvasRef;

[Inject]
internal IJSRuntime JSRuntime { get; set; }
}
}
9 changes: 7 additions & 2 deletions src/Blazor.Extensions.Canvas/Blazor.Extensions.Canvas.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,14 @@
<TargetsForTfmSpecificBuildOutput>$(TargetsForTfmSpecificBuildOutput);IncludeP2POutput</TargetsForTfmSpecificBuildOutput>
</PropertyGroup>

<PropertyGroup>
<LangVersion>Preview</LangVersion>
<RazorLangVersion>3.0</RazorLangVersion>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Blazor.Browser" Version="0.7.0" />
<PackageReference Include="Microsoft.AspNetCore.Blazor.Build" Version="0.7.0" />
<PackageReference Include="Microsoft.AspNetCore.Blazor" Version="3.0.0-preview4-19216-03" />
<PackageReference Include="Microsoft.AspNetCore.Blazor.Build" Version="3.0.0-preview4-19216-03" PrivateAssets="all" />
</ItemGroup>

<ItemGroup>
Expand Down
Loading

0 comments on commit f9d1bc3

Please sign in to comment.