2 Commits

Author SHA1 Message Date
Jedd Morgan 71f32a5f71 fix(ci): correct build output path (#29)
.NET Build and Publish / build (push) Has been cancelled
* fix ci

* output folder

* correct mistake

* pbulish all nugets
2025-06-13 18:50:27 +01:00
Jedd Morgan eb97eb1df8 refactor: Add sourcelink and Github Actions (#28)
* Quick pass

* no locks

* github actions

* publish via github actions

* pack
2025-06-13 17:29:55 +01:00
18 changed files with 942 additions and 658 deletions
-52
View File
@@ -1,52 +0,0 @@
version: 2.1
orbs:
win: circleci/windows@5.0.0
jobs:
build:
executor: win/default
steps:
- checkout
- run:
name: Restore packages
command: dotnet restore GrasshopperAsyncComponent.sln
- run:
name: Build solution
command: |
$TAG = if([string]::IsNullOrEmpty($env:CIRCLE_TAG)) { "0.999.$($env:WORKFLOW_NUM)-ci" } else { $env:CIRCLE_TAG }
dotnet build GrasshopperAsyncComponent.sln --no-restore -c Release /p:Version=$TAG /p:AssemblyVersionNumber=$TAG /p:AssemblyInformationalVersion=$TAG
environment:
WORKFLOW_NUM: << pipeline.number >>
publish_nuget:
executor: win/default
steps:
- checkout
- run:
name: Restore packages
command: dotnet restore GrasshopperAsyncComponent.sln
- run:
name: Build solution
command: |
$TAG = if([string]::IsNullOrEmpty($env:CIRCLE_TAG)) { "0.999.$($env:WORKFLOW_NUM)-ci" } else { $env:CIRCLE_TAG }
dotnet build GrasshopperAsyncComponent.sln --no-restore -c Release /p:Version=$TAG /p:AssemblyVersionNumber=$TAG /p:AssemblyInformationalVersion=$TAG
environment:
WORKFLOW_NUM: << pipeline.number >>
- run:
name: Push NuGet
command: nuget push **/*.nupkg -Source https://api.nuget.org/v3/index.json -ApiKey $env:NUGET_APIKEY -SkipDuplicate
workflows:
build:
jobs:
- build:
context: github-dev-bot
publish:
jobs:
- publish_nuget:
filters:
tags:
only: /.*/
branches:
ignore: /.*/
context: nuget
+26
View File
@@ -0,0 +1,26 @@
Directory.Build.targets
Directory.Build.props
**/bin/*
**/obj/*
_ReSharper.SharpCompress/
bin/
*.suo
*.user
TestArchives/Scratch/
TestArchives/Scratch2/
TestResults/
*.nupkg
packages/*/
project.lock.json
tests/TestArchives/Scratch
.vs
tools
.vscode
.idea/
.DS_Store
*.snupkg
coverage.xml
*.received.*
+7
View File
@@ -0,0 +1,7 @@
printWidth: 120
useTabs: false
indentSize: 2
preprocessorSymbolSets:
- ""
- "DEBUG"
- "DEBUG,CODE_STYLE"
+327
View File
@@ -0,0 +1,327 @@
root = true
# Don't use tabs for indentation.
[*]
indent_style = space
# Microsoft .NET properties
csharp_using_directive_placement = outside_namespace:silent
dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none
dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary:none
dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none
# Standard properties
insert_final_newline = true
# (Please don't specify an indent_size here; that has too many unintended consequences.)
# Code files
[*.{cs,csx,vb,vbx}]
indent_size = 2
charset = utf-8
# Xml project files
[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]
indent_size = 2
space_after_last_pi_attribute = false
# Xml config files
[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}]
indent_size = 2
space_after_last_pi_attribute = false
# JSON files
[*.json]
indent_size = 2
# Dotnet code style settings:
[*.{cs,vb}]
# Sort using and Import directives with System.* appearing first
dotnet_sort_system_directives_first = true
dotnet_separate_import_directive_groups = false
# Avoid "this." and "Me." if not necessary
dotnet_style_qualification_for_field = false:suggestion
dotnet_style_qualification_for_property = false:suggestion
dotnet_style_qualification_for_method = false:suggestion
dotnet_style_qualification_for_event = false:suggestion
# Use language keywords instead of framework type names for type references
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
dotnet_style_predefined_type_for_member_access = true:suggestion
# Parentheses preferences
dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:silent
dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:silent
dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary:silent
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
# Modifier preferences
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
dotnet_style_readonly_field = true:suggestion
# Expression-level preferences
dotnet_style_object_initializer = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
dotnet_style_prefer_inferred_tuple_names = true:suggestion
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_style_prefer_auto_properties = true:warning
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
dotnet_style_prefer_conditional_expression_over_return = true:silent
# CSharp code style settings:
[*.cs]
# Prefer "var" everywhere
csharp_style_var_elsewhere = false:none
csharp_style_var_for_built_in_types = false:none
csharp_style_var_when_type_is_apparent = false:none
# Prefer method-like constructs to have a block body
csharp_style_expression_bodied_methods = true:suggestion
csharp_style_expression_bodied_constructors = false:suggestion
csharp_style_expression_bodied_operators = true:suggestion
# Prefer property-like constructs to have an expression-body
csharp_style_expression_bodied_properties = true:suggestion
csharp_style_expression_bodied_indexers = true:suggestion
csharp_style_expression_bodied_accessors = true:suggestion
# Suggest more modern language features when available
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
csharp_style_throw_expression = true:suggestion
csharp_style_conditional_delegate_call = true:suggestion
csharp_style_namespace_declarations = file_scoped
# Newline settings
csharp_new_line_before_open_brace = all
csharp_new_line_before_else = true
csharp_new_line_before_catch = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_members_in_anonymous_types = true
# Space preferences
csharp_space_after_cast = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_around_binary_operators = before_and_after
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
# Wrapping preferences
csharp_preserve_single_line_statements = true
csharp_preserve_single_line_blocks = true
# SYMBOL NAMING RULES
# Copied from https://github.com/dotnet/roslyn/blob/main/.editorconfig
# Adapted rules:
# - Constants are ALL_UPPER
# - Non-private fields are PascalCase
# Non-private fields are PascalCase
dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.severity = warning
dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.symbols = non_private_readonly_fields
dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.style = non_private_readonly_field_style
dotnet_naming_symbols.non_private_readonly_fields.applicable_kinds = field
dotnet_naming_symbols.non_private_readonly_fields.applicable_accessibilities = public, protected, internal, protected_internal, private_protected
dotnet_naming_style.non_private_readonly_field_style.capitalization = pascal_case
# Constants are ALL_UPPER
dotnet_naming_rule.constants_should_be_all_upper.severity = warning
dotnet_naming_rule.constants_should_be_all_upper.symbols = constants
dotnet_naming_rule.constants_should_be_all_upper.style = constant_style
dotnet_naming_symbols.constants.applicable_kinds = field, local
dotnet_naming_symbols.constants.required_modifiers = const
dotnet_naming_style.constant_style.capitalization = all_upper
# Private static fields are camelCase and start with s_
dotnet_naming_rule.static_fields_should_be_camel_case.severity = warning
dotnet_naming_rule.static_fields_should_be_camel_case.symbols = static_fields
dotnet_naming_rule.static_fields_should_be_camel_case.style = static_field_style
dotnet_naming_symbols.static_fields.applicable_accessibilities = private
dotnet_naming_symbols.static_fields.applicable_kinds = field
dotnet_naming_symbols.static_fields.required_modifiers = static
dotnet_naming_style.static_field_style.capitalization = camel_case
dotnet_naming_style.static_field_style.required_prefix = s_
# Instance fields are camelCase and start with _
dotnet_naming_rule.instance_fields_should_be_camel_case.severity = warning
dotnet_naming_rule.instance_fields_should_be_camel_case.symbols = instance_fields
dotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_style
dotnet_naming_symbols.instance_fields.applicable_kinds = field
dotnet_naming_style.instance_field_style.capitalization = camel_case
dotnet_naming_style.instance_field_style.required_prefix = _
# Locals and parameters are camelCase
dotnet_naming_rule.locals_should_be_camel_case.severity = warning
dotnet_naming_rule.locals_should_be_camel_case.symbols = locals_and_parameters
dotnet_naming_rule.locals_should_be_camel_case.style = camel_case_style
dotnet_naming_symbols.locals_and_parameters.applicable_kinds = parameter, local
dotnet_naming_style.camel_case_style.capitalization = camel_case
# Local functions are PascalCase
dotnet_naming_rule.local_functions_should_be_pascal_case.severity = warning
dotnet_naming_rule.local_functions_should_be_pascal_case.symbols = local_functions
dotnet_naming_rule.local_functions_should_be_pascal_case.style = local_function_style
dotnet_naming_symbols.local_functions.applicable_kinds = local_function
dotnet_naming_style.local_function_style.capitalization = pascal_case
# By default, name items with PascalCase
dotnet_naming_rule.members_should_be_pascal_case.severity = warning
dotnet_naming_rule.members_should_be_pascal_case.symbols = all_members
dotnet_naming_rule.members_should_be_pascal_case.style = pascal_case_style
dotnet_naming_symbols.all_members.applicable_kinds = *
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
# Analyzer settings
dotnet_analyzer_diagnostic.category-Style.severity = warning # All rules will use this severity unless overriden
dotnet_diagnostic.ide0055.severity = none # Formatting rule: Incompatible with CSharpier
dotnet_diagnostic.ide0007.severity = none # Use var instead of explicit type: Preference
dotnet_diagnostic.ide0009.severity = none # Add this or Me qualification: Preference
dotnet_diagnostic.ide0200.severity = none # Remove unnecessary lambda expression: may be performance reasons not to
dotnet_diagnostic.ide0058.severity = none # Remove unnecessary expression value: Subjective
dotnet_diagnostic.ide0010.severity = none # Add missing cases to switch statement: Too verbose
dotnet_diagnostic.ide0200.severity = none # Remove unnecessary lambda expression: may be performance reasons not to
dotnet_diagnostic.ide0058.severity = none # Remove unnecessary expression value: Subjective
dotnet_diagnostic.ide0305.severity = none # Use collection expression for fluent: Can obfuscate intent
dotnet_diagnostic.ide0001.severity = suggestion # Name can be simplified: Non enforceable in build
dotnet_diagnostic.ide0046.severity = suggestion # Use conditional expression for return: Subjective
dotnet_diagnostic.ide0045.severity = suggestion # Use conditional expression for assignment: Subjective
dotnet_diagnostic.ide0078.severity = suggestion # Use pattern matching: Subjective
dotnet_diagnostic.ide0260.severity = suggestion # Use pattern matching: Subjective
dotnet_diagnostic.ide0022.severity = suggestion # Use expression body for method: Subjective
dotnet_diagnostic.ide0061.severity = suggestion # Use expression body for local functions: Subjective
dotnet_diagnostic.ide0063.severity = suggestion # Using directive can be simplified
dotnet_diagnostic.ide0066.severity = suggestion # Use switch expression: Subjective
dotnet_diagnostic.ide0029.severity = suggestion # Null check can be simplified: Subjective
dotnet_diagnostic.ide0030.severity = suggestion # Null check can be simplified: Subjective
dotnet_diagnostic.ide0270.severity = suggestion # Null check can be simplified: Subjective
dotnet_diagnostic.ide0042.severity = suggestion # Deconstruct variable declaration: Subjective
dotnet_diagnostic.ide0039.severity = suggestion # Use local function instead of lambda: Subjective
dotnet_diagnostic.ide0029.severity = suggestion # Null check can be simplified: Subjective
dotnet_diagnostic.ide0030.severity = suggestion # Null check can be simplified: Subjective
dotnet_diagnostic.ide0270.severity = suggestion # Null check can be simplified: Subjective
dotnet_diagnostic.ide0042.severity = suggestion # Deconstruct variable declaration: Subjective
dotnet_diagnostic.ide0028.severity = suggestion # Use collection initializers: Subjective
dotnet_diagnostic.ide0072.severity = suggestion # Populate switch statement: Subjective
dotnet_diagnostic.ide0074.severity = suggestion # Use compound assignment: Subjective
dotnet_diagnostic.ide0300.severity = suggestion # Use collection expression for array: Subjective, maybe aspirational
dotnet_diagnostic.ide0290.severity = suggestion # primary constructors: subjective, and readonly properties are not a thing
dotnet_diagnostic.ide0290.severity = suggestion # Use primary constructor: Subjective
dotnet_diagnostic.ide0037.severity = suggestion # Use inferred member names: Sometimes its nice to be explicit
dotnet_diagnostic.ide0301.severity = suggestion # Use collection expression for empty: Subjective, intent
dotnet_diagnostic.ide0021.severity = suggestion # Use expression body for constructors : Subjective
dotnet_diagnostic.ide0090.severity = suggestion # Simplify new expression : Subjective
dotnet_diagnostic.ide0047.severity = suggestion # Parentheses preferences: IDEs don't properly pick it up
dotnet_diagnostic.ide0130.severity = suggestion # Namespace does not match folder structure : Aspirational
dotnet_diagnostic.ide1006.severity = suggestion # Naming rule violation : Aspirational
# Maintainability rules
dotnet_diagnostic.ca1501.severity = warning # Avoid excessive inheritance
dotnet_diagnostic.ca1502.severity = warning # Avoid excessive complexity
dotnet_diagnostic.ca1505.severity = warning # Avoid unmaintainable code
dotnet_diagnostic.ca1506.severity = warning # Avoid excessive class coupling
dotnet_diagnostic.ca1507.severity = warning # Use nameof in place of string
dotnet_diagnostic.ca1508.severity = warning # Avoid dead conditional code
dotnet_diagnostic.ca1509.severity = warning # Invalid entry in code metrics configuration file
dotnet_diagnostic.ca1861.severity = suggestion # Prefer 'static readonly' fields over constant array arguments if the called method is called repeatedly and is not mutating the passed array (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1861)
# Performance rules
dotnet_diagnostic.ca1849.severity = suggestion # Call async methods when in an async method: May decrease performance
dotnet_diagnostic.ca1822.severity = suggestion # Mark member as static
dotnet_diagnostic.ca1859.severity = suggestion # Use concrete types when possible for improved performance
# Design rule
dotnet_diagnostic.ca1002.severity = suggestion # Do not expose generic lists
dotnet_diagnostic.ca1051.severity = warning # Do not declare visible instance fields
dotnet_diagnostic.ca1056.severity = suggestion # URI properties should not be strings
dotnet_diagnostic.ca1062.severity = none # Public method must check all parameters for null
# Naming
dotnet_diagnostic.ca1707.severity = none # Remove underscores in names
# Usage
dotnet_diagnostic.ca2227.severity = suggestion # Collection props should be read-only
dotnet_code_quality.ca1051.exclude_structs = true # CA1051 is excluded in structs
dotnet_code_quality.dispose_ownership_transfer_at_constructor = true # CA2000 has a lot of false positives without this
dotnet_code_quality.dispose_ownership_transfer_at_method_call = true # CA2000 has a lot of false positives without this
dotnet_code_quality.dispose_analysis_kind = NonExceptionPathsOnlyNotDisposed # CA2000 has a lot of false positives without this
# NUnit
dotnet_diagnostic.NUnit2001.severity = warning # Consider using Assert.That(expr, Is.False) instead of Assert.False(expr)
dotnet_diagnostic.NUnit2002.severity = warning # Consider using Assert.That(expr, Is.False) instead of Assert.IsFalse(expr)
dotnet_diagnostic.NUnit2003.severity = warning # Consider using Assert.That(expr, Is.True) instead of Assert.IsTrue(expr)
dotnet_diagnostic.NUnit2004.severity = warning # Consider using Assert.That(expr, Is.True) instead of Assert.True(expr)
dotnet_diagnostic.NUnit2005.severity = warning # Consider using Assert.That(actual, Is.EqualTo(expected)) instead of Assert.AreEqual(expected, actual)
dotnet_diagnostic.NUnit2006.severity = warning # Consider using Assert.That(actual, Is.Not.EqualTo(expected)) instead of Assert.AreNotEqual(expected, actual)
dotnet_diagnostic.NUnit2010.severity = warning # Use EqualConstraint for better assertion messages in case of failure
dotnet_diagnostic.NUnit2011.severity = warning # Use ContainsConstraint for better assertion messages in case of failure
dotnet_diagnostic.NUnit2011.severity = warning # Use StartsWithConstraint for better assertion messages in case of failure
dotnet_diagnostic.NUnit2011.severity = warning # Use EndsWithConstraint for better assertion messages in case of failure
dotnet_diagnostic.NUnit2014.severity = warning # Use SomeItemsConstraint for better assertion messages in case of failure
dotnet_diagnostic.NUnit2015.severity = warning # Consider using Assert.That(actual, Is.SameAs(expected)) instead of Assert.AreSame(expected, actual)
dotnet_diagnostic.NUnit2016.severity = warning # Consider using Assert.That(expr, Is.Null) instead of Assert.Null(expr)
dotnet_diagnostic.NUnit2017.severity = warning # Consider using Assert.That(expr, Is.Null) instead of Assert.IsNull(expr)
dotnet_diagnostic.NUnit2018.severity = warning # Consider using Assert.That(expr, Is.Not.Null) instead of Assert.NotNull(expr)
dotnet_diagnostic.NUnit2028.severity = warning # Consider using Assert.That(actual, Is.GreaterThanOrEqualTo(expected)) instead of Assert.GreaterOrEqual(actual, expected)
dotnet_diagnostic.NUnit2027.severity = warning # Consider using Assert.That(actual, Is.GreaterThan(expected)) instead of Assert.Greater(actual, expected)
dotnet_diagnostic.NUnit2029.severity = warning # Consider using Assert.That(actual, Is.LessThan(expected)) instead of Assert.Less(actual, expected)
dotnet_diagnostic.NUnit2030.severity = warning # Consider using Assert.That(actual, Is.LessThanOrEqualTo(expected)) instead of Assert.LessOrEqual(actual, expected)
dotnet_diagnostic.NUnit2031.severity = warning # Consider using Assert.That(actual, Is.Not.SameAs(expected)) instead of Assert.AreNotSame(expected, actual)
dotnet_diagnostic.NUnit2032.severity = warning # Consider using Assert.That(expr, Is.Zero) instead of Assert.Zero(expr)
dotnet_diagnostic.NUnit2033.severity = warning # Consider using Assert.That(expr, Is.Not.Zero) instead of Assert.NotZero(expr)
dotnet_diagnostic.NUnit2034.severity = warning # Consider using Assert.That(expr, Is.NaN) instead of Assert.IsNaN(expr)
dotnet_diagnostic.NUnit2035.severity = warning # Consider using Assert.That(collection, Is.Empty) instead of Assert.IsEmpty(collection)
dotnet_diagnostic.NUnit2036.severity = warning # Consider using Assert.That(collection, Is.Not.Empty) instead of Assert.IsNotEmpty(collection)
dotnet_diagnostic.NUnit2037.severity = warning # Consider using Assert.That(collection, Does.Contain(instance)) instead of Assert.Contains(instance, collection)
dotnet_diagnostic.NUnit2038.severity = warning # Consider using Assert.That(actual, Is.InstanceOf(expected)) instead of Assert.IsInstanceOf(expected, actual)
dotnet_diagnostic.NUnit2039.severity = warning # Consider using Assert.That(actual, Is.Not.InstanceOf(expected)) instead of Assert.IsNotInstanceOf(expected, actual)
[*.{appxmanifest,asax,ascx,aspx,axaml,build,c,c++,cc,cginc,compute,cp,cpp,cs,cshtml,cu,cuh,cxx,dtd,fs,fsi,fsscript,fsx,fx,fxh,h,hh,hlsl,hlsli,hlslinc,hpp,hxx,inc,inl,ino,ipp,ixx,master,ml,mli,mpp,mq4,mq5,mqh,nuspec,paml,razor,resw,resx,shader,skin,tpp,usf,ush,vb,xaml,xamlx,xoml,xsd}]
indent_style = space
indent_size = 2
tab_width = 2
# Verify
[*.{received,verified}.{json}]
charset = utf-8-bom
end_of_line = lf
indent_size = unset
indent_style = unset
insert_final_newline = false
tab_width = unset
trim_trailing_whitespace = false
-77
View File
@@ -1,77 +0,0 @@
name: Update issue Status
on:
issues:
types: [closed]
jobs:
update_issue:
runs-on: ubuntu-latest
steps:
- name: Get project data
env:
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
ORGANIZATION: specklesystems
PROJECT_NUMBER: 9
run: |
gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
query($org: String!, $number: Int!) {
organization(login: $org){
projectNext(number: $number) {
id
fields(first:20) {
nodes {
id
name
settings
}
}
}
}
}' -f org=$ORGANIZATION -F number=$PROJECT_NUMBER > project_data.json
echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV
echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV
echo "$PROJECT_ID"
echo "$STATUS_FIELD_ID"
echo 'DONE_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .settings | fromjson | .options[] | select(.name== "Done") | .id' project_data.json) >> $GITHUB_ENV
echo "$DONE_ID"
- name: Add Issue to project #it's already in the project, but we do this to get its node id!
env:
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
ISSUE_ID: ${{ github.event.issue.node_id }}
run: |
item_id="$( gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
mutation($project:ID!, $id:ID!) {
addProjectNextItem(input: {projectId: $project, contentId: $id}) {
projectNextItem {
id
}
}
}' -f project=$PROJECT_ID -f id=$ISSUE_ID --jq '.data.addProjectNextItem.projectNextItem.id')"
echo 'ITEM_ID='$item_id >> $GITHUB_ENV
- name: Update Status
env:
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
ISSUE_ID: ${{ github.event.issue.node_id }}
run: |
gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
mutation($project:ID!, $status:ID!, $id:ID!, $value:String!) {
set_status: updateProjectNextItemField(
input: {
projectId: $project
itemId: $id
fieldId: $status
value: $value
}
) {
projectNextItem {
id
}
}
}' -f project=$PROJECT_ID -f status=$STATUS_FIELD_ID -f id=$ITEM_ID -f value=${{ env.DONE_ID }}
-50
View File
@@ -1,50 +0,0 @@
name: Move new issues into Project
on:
issues:
types: [opened]
jobs:
track_issue:
runs-on: ubuntu-latest
steps:
- name: Get project data
env:
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
ORGANIZATION: specklesystems
PROJECT_NUMBER: 9
run: |
gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
query($org: String!, $number: Int!) {
organization(login: $org){
projectNext(number: $number) {
id
fields(first:20) {
nodes {
id
name
settings
}
}
}
}
}' -f org=$ORGANIZATION -F number=$PROJECT_NUMBER > project_data.json
echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV
echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV
- name: Add Issue to project
env:
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
ISSUE_ID: ${{ github.event.issue.node_id }}
run: |
item_id="$( gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
mutation($project:ID!, $id:ID!) {
addProjectNextItem(input: {projectId: $project, contentId: $id}) {
projectNextItem {
id
}
}
}' -f project=$PROJECT_ID -f id=$ISSUE_ID --jq '.data.addProjectNextItem.projectNextItem.id')"
echo 'ITEM_ID='$item_id >> $GITHUB_ENV
-50
View File
@@ -1,50 +0,0 @@
name: Move new issues into Project
on:
issues:
types: [opened]
jobs:
track_issue:
runs-on: ubuntu-latest
steps:
- name: Get project data
env:
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
ORGANIZATION: specklesystems
PROJECT_NUMBER: 9
run: |
gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
query($org: String!, $number: Int!) {
organization(login: $org){
projectNext(number: $number) {
id
fields(first:20) {
nodes {
id
name
settings
}
}
}
}
}' -f org=$ORGANIZATION -F number=$PROJECT_NUMBER > project_data.json
echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV
echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV
- name: Add Issue to project
env:
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
ISSUE_ID: ${{ github.event.issue.node_id }}
run: |
item_id="$( gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
mutation($project:ID!, $id:ID!) {
addProjectNextItem(input: {projectId: $project, contentId: $id}) {
projectNextItem {
id
}
}
}' -f project=$PROJECT_ID -f id=$ISSUE_ID --jq '.data.addProjectNextItem.projectNextItem.id')"
echo 'ITEM_ID='$item_id >> $GITHUB_ENV
+57
View File
@@ -0,0 +1,57 @@
name: .NET Build and Publish
on:
pull_request:
push:
tags: ["v*.*.*"]
jobs:
build:
env:
SOLUTION_NAME: "GrasshopperAsyncComponent.sln"
OUTPUT_PATH: "output"
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.x.x
- id: set-version
name: Set version to output
run: |
TAG=${{ github.ref_name }}
if [[ "${{ github.ref }}" != refs/tags/* ]]; then
TAG="v0.0.99.${{ github.run_number }}"
fi
SEMVER="${TAG#v}"
FILE_VERSION=$(echo "$TAG" | sed -E 's/^v([0-9]+\.[0-9]+\.[0-9]+).*/\1/')
FILE_VERSION="$FILE_VERSION.${{ github.run_number }}"
echo "semver=$SEMVER" >> "$GITHUB_OUTPUT"
echo "fileVersion=$FILE_VERSION" >> "$GITHUB_OUTPUT"
echo $SEMVER
echo $FILE_VERSION
- name: restore
run: dotnet restore ${{env.SOLUTION_NAME}}
- name: build
run: |
dotnet build ${{env.SOLUTION_NAME}} \
--configuration release \
--no-restore \
-warnaserror \
-p:Version=${{steps.set-version.outputs.semver}} \
-p:FileVersion=${{steps.set-version.outputs.fileVersion}}
- name: pack
run: dotnet pack ${{env.SOLUTION_NAME}} --no-build -p:Version=${{steps.set-version.outputs.semver}} -p:FileVersion=${{steps.set-version.outputs.fileVersion}}
- name: Push to nuget.org
if: (github.ref_type == 'tag')
run: dotnet nuget push **/*.nupkg --source "https://api.nuget.org/v3/index.json" --api-key ${{secrets.CONNECTORS_NUGET_TOKEN }} --skip-duplicate
+4
View File
@@ -0,0 +1,4 @@
CA1502: 25
CA1501: 5
CA1506(Method): 50
CA1506(Type): 95
+69
View File
@@ -0,0 +1,69 @@
<Project>
<PropertyGroup Label="Compiler Properties">
<LangVersion>12</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<PropertyGroup Label="Nugetspec Package Properties">
<!-- Defines common Nugetspec properties -->
<!-- Inheriting packable projects should define the rest of the nugetspec properties (PackageId, Description) -->
<!-- and may, if needed, override/extend any of these (e.g. PackageTags) -->
<Authors>Speckle</Authors>
<Copyright>Copyright (c) AEC Systems Ltd</Copyright>
<PackageProjectUrl>https://speckle.systems/</PackageProjectUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/specklesystems/GrasshopperAsyncComponent</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>speckle</PackageTags>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
</PropertyGroup>
<PropertyGroup Label="Nuget Package Properties">
<IsPackable>false</IsPackable>
<!--Can be set to true in inheriting .props/.csproj files for projects that should be packed-->
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
</PropertyGroup>
<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>
<PropertyGroup Label="Analyers">
<EnableNetAnalyzers>true</EnableNetAnalyzers>
<AnalysisLevel>latest-AllEnabledByDefault</AnalysisLevel>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<!-- Ingored warnings, some aspirational but too noisy for now, some by design. -->
<NoWarn>
<!--Disabled by design-->
CA5399;CA1812;
<!--XML comment-->
CS1591;CS1573;
<!-- Globalization rules -->
CA1303;CA1304;CA1305;CA1307;CA1308;CA1309;CA1310;CA1311;
<!-- Logging -->
CA1848;CA1727;
<!-- Others we don't want -->
CA1815;CA1725;
<!-- Naming things is hard enough -->
CA1710;CA1711;CA1720;CA1724;
<!-- Aspirational -->
CA1502;CA1716;NETSDK1206;
$(NoWarn)
</NoWarn
>
</PropertyGroup>
<PropertyGroup>
<!-- Expose the repository root to all projects -->
<RepositoryRoot>$(MSBuildThisFileDirectory)</RepositoryRoot>
</PropertyGroup>
<ItemGroup>
<None Include="..\README.md" Pack="true" PackagePath="\" />
</ItemGroup>
<ItemGroup>
<!-- This file contains the configuration for some analyzer warnings, such as cyclomatic
complexity threshold -->
<AdditionalFiles Include="$(RepositoryRoot)CodeMetricsConfig.txt" />
</ItemGroup>
</Project>
+8
View File
@@ -7,6 +7,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GrasshopperAsyncComponent",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GrasshopperAsyncComponentDemo", "GrasshopperAsyncComponentDemo\GrasshopperAsyncComponentDemo.csproj", "{695D2B91-DDB6-416E-8A99-DDE6253DA7AA}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "config", "config", "{B84A4AEC-CAA4-4065-89A4-E7FBB3889D25}"
ProjectSection(SolutionItems) = preProject
Directory.Build.props = Directory.Build.props
.editorconfig = .editorconfig
.csharpierrc.yaml = .csharpierrc.yaml
CodeMetricsConfig.txt = CodeMetricsConfig.txt
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
+189 -171
View File
@@ -1,249 +1,267 @@
using Grasshopper.Kernel;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Grasshopper.Kernel;
using Timer = System.Timers.Timer;
namespace GrasshopperAsyncComponent
{
/// <summary>
/// Inherit your component from this class to make all the async goodness available.
/// </summary>
public abstract class GH_AsyncComponent : GH_Component
{
public override Guid ComponentGuid => throw new Exception("ComponentGuid should be overriden in any descendant of GH_AsyncComponent!");
namespace GrasshopperAsyncComponent;
/// <summary>
/// Inherit your component from this class to make all the async goodness available.
/// </summary>
public abstract class GH_AsyncComponent : GH_Component, IDisposable
{
//List<(string, GH_RuntimeMessageLevel)> Errors;
Action<string, double> ReportProgress;
private readonly Action<string, double> _reportProgress;
public ConcurrentDictionary<string, double> ProgressReports;
public ConcurrentDictionary<string, double> ProgressReports { get; protected set; }
Action Done;
private readonly Action _done;
Timer DisplayProgressTimer;
private readonly Timer _displayProgressTimer;
int State = 0;
private int _state;
int SetData = 0;
private int _setData;
public List<WorkerInstance> Workers;
public List<WorkerInstance> Workers { get; protected set; }
List<Task> Tasks;
private readonly List<Task> _tasks;
public readonly List<CancellationTokenSource> CancellationSources;
public List<CancellationTokenSource> CancellationSources { get; }
/// <summary>
/// Set this property inside the constructor of your derived component.
/// Set this property inside the constructor of your derived component.
/// </summary>
public WorkerInstance BaseWorker { get; set; }
public WorkerInstance? BaseWorker { get; set; }
/// <summary>
/// Optional: if you have opinions on how the default system task scheduler should treat your workers, set it here.
/// </summary>
public TaskCreationOptions? TaskCreationOptions { get; set; } = null;
public TaskCreationOptions? TaskCreationOptions { get; set; }
protected GH_AsyncComponent(string name, string nickname, string description, string category, string subCategory) : base(name, nickname, description, category, subCategory)
protected GH_AsyncComponent(string name, string nickname, string description, string category, string subCategory)
: base(name, nickname, description, category, subCategory)
{
Workers = new List<WorkerInstance>();
CancellationSources = new List<CancellationTokenSource>();
_tasks = new List<Task>();
ProgressReports = new ConcurrentDictionary<string, double>();
DisplayProgressTimer = new Timer(333) { AutoReset = false };
DisplayProgressTimer.Elapsed += DisplayProgress;
_displayProgressTimer = new Timer(333) { AutoReset = false };
_displayProgressTimer.Elapsed += DisplayProgress;
ReportProgress = (id, value) =>
{
ProgressReports[id] = value;
if (!DisplayProgressTimer.Enabled)
_reportProgress = (id, value) =>
{
DisplayProgressTimer.Start();
}
};
ProgressReports[id] = value;
if (!_displayProgressTimer.Enabled)
{
_displayProgressTimer.Start();
}
};
Done = () =>
{
Interlocked.Increment(ref State);
if (State == Workers.Count && SetData == 0)
_done = () =>
{
Interlocked.Exchange(ref SetData, 1);
Interlocked.Increment(ref _state);
if (_state == Workers.Count && _setData == 0)
{
Interlocked.Exchange(ref _setData, 1);
// We need to reverse the workers list to set the outputs in the same order as the inputs.
Workers.Reverse();
// We need to reverse the workers list to set the outputs in the same order as the inputs.
Workers.Reverse();
Rhino.RhinoApp.InvokeOnUiThread((Action)delegate
{
ExpireSolution(true);
});
}
};
ProgressReports = new ConcurrentDictionary<string, double>();
Workers = new List<WorkerInstance>();
CancellationSources = new List<CancellationTokenSource>();
Tasks = new List<Task>();
Rhino.RhinoApp.InvokeOnUiThread(
(Action)
delegate
{
ExpireSolution(true);
}
);
}
};
}
public virtual void DisplayProgress(object sender, System.Timers.ElapsedEventArgs e)
{
if (Workers.Count == 0 || ProgressReports.Values.Count == 0)
{
return;
}
if (Workers.Count == 1)
{
Message = ProgressReports.Values.Last().ToString("0.00%");
}
else
{
double total = 0;
foreach (var kvp in ProgressReports)
if (Workers.Count == 0 || ProgressReports.Values.Count == 0)
{
total += kvp.Value;
return;
}
Message = (total / Workers.Count).ToString("0.00%");
}
if (Workers.Count == 1)
{
Message = ProgressReports.Values.Last().ToString("0.00%");
}
else
{
double total = 0;
foreach (var kvp in ProgressReports)
{
total += kvp.Value;
}
Rhino.RhinoApp.InvokeOnUiThread((Action)delegate
{
OnDisplayExpired(true);
});
Message = (total / Workers.Count).ToString("0.00%");
}
Rhino.RhinoApp.InvokeOnUiThread(
(Action)
delegate
{
OnDisplayExpired(true);
}
);
}
protected override void BeforeSolveInstance()
{
if (State != 0 && SetData == 1)
{
return;
}
if (_state != 0 && _setData == 1)
{
return;
}
Debug.WriteLine("Killing");
Debug.WriteLine("Killing");
foreach (var source in CancellationSources)
{
source.Cancel();
}
foreach (var source in CancellationSources)
{
source.Cancel();
}
CancellationSources.Clear();
Workers.Clear();
ProgressReports.Clear();
Tasks.Clear();
CancellationSources.Clear();
Workers.Clear();
ProgressReports.Clear();
_tasks.Clear();
Interlocked.Exchange(ref State, 0);
Interlocked.Exchange(ref _state, 0);
}
protected override void AfterSolveInstance()
{
System.Diagnostics.Debug.WriteLine("After solve instance was called " + State + " ? " + Workers.Count);
// We need to start all the tasks as close as possible to each other.
if (State == 0 && Tasks.Count > 0 && SetData == 0)
{
System.Diagnostics.Debug.WriteLine("After solve INVOKATIONM");
foreach (var task in Tasks)
System.Diagnostics.Debug.WriteLine("After solve instance was called " + _state + " ? " + Workers.Count);
// We need to start all the tasks as close as possible to each other.
if (_state == 0 && _tasks.Count > 0 && _setData == 0)
{
task.Start();
System.Diagnostics.Debug.WriteLine("After solve INVOKATION");
foreach (var task in _tasks)
{
task.Start();
}
}
}
}
protected override void ExpireDownStreamObjects()
{
// Prevents the flash of null data until the new solution is ready
if (SetData == 1)
{
base.ExpireDownStreamObjects();
}
// Prevents the flash of null data until the new solution is ready
if (_setData == 1)
{
base.ExpireDownStreamObjects();
}
}
protected override void SolveInstance(IGH_DataAccess DA)
protected override void SolveInstance(IGH_DataAccess da)
{
//return;
if (State == 0)
{
if (BaseWorker == null)
//return;
if (_state == 0)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Worker class not provided.");
return;
if (BaseWorker == null)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Worker class not provided.");
return;
}
// Add cancellation source to our bag
var tokenSource = new CancellationTokenSource();
CancellationSources.Add(tokenSource);
var currentWorker = BaseWorker.Duplicate($"Worker-{da.Iteration}", tokenSource.Token);
if (currentWorker == null)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Could not get a worker instance.");
return;
}
// Let the worker collect data.
currentWorker.GetData(da, Params);
var currentRun =
TaskCreationOptions != null
? new Task(
() => currentWorker.DoWork(_reportProgress, _done),
tokenSource.Token,
(TaskCreationOptions)TaskCreationOptions
)
: new Task(() => currentWorker.DoWork(_reportProgress, _done), tokenSource.Token);
// Add the worker to our list
Workers.Add(currentWorker);
_tasks.Add(currentRun);
return;
}
var currentWorker = BaseWorker.Duplicate();
if (currentWorker == null)
if (_setData == 0)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Could not get a worker instance.");
return;
return;
}
// Let the worker collect data.
currentWorker.GetData(DA, Params);
if (Workers.Count > 0)
{
Interlocked.Decrement(ref _state);
Workers[_state].SetData(da);
}
// Create the task
var tokenSource = new CancellationTokenSource();
currentWorker.CancellationToken = tokenSource.Token;
currentWorker.Id = $"Worker-{DA.Iteration}";
if (_state != 0)
{
return;
}
var currentRun = TaskCreationOptions != null
? new Task(() => currentWorker.DoWork(ReportProgress, Done), tokenSource.Token, (TaskCreationOptions)TaskCreationOptions)
: new Task(() => currentWorker.DoWork(ReportProgress, Done), tokenSource.Token);
CancellationSources.Clear();
Workers.Clear();
ProgressReports.Clear();
_tasks.Clear();
// Add cancellation source to our bag
CancellationSources.Add(tokenSource);
Interlocked.Exchange(ref _setData, 0);
// Add the worker to our list
Workers.Add(currentWorker);
Tasks.Add(currentRun);
return;
}
if (SetData == 0)
{
return;
}
if (Workers.Count > 0)
{
Interlocked.Decrement(ref State);
Workers[State].SetData(DA);
}
if (State != 0)
{
return;
}
CancellationSources.Clear();
Workers.Clear();
ProgressReports.Clear();
Tasks.Clear();
Interlocked.Exchange(ref SetData, 0);
Message = "Done";
OnDisplayExpired(true);
Message = "Done";
OnDisplayExpired(true);
}
public void RequestCancellation()
{
foreach (var source in CancellationSources)
{
source.Cancel();
}
foreach (var source in CancellationSources)
{
source.Cancel();
}
CancellationSources.Clear();
Workers.Clear();
ProgressReports.Clear();
Tasks.Clear();
CancellationSources.Clear();
Workers.Clear();
ProgressReports.Clear();
_tasks.Clear();
Interlocked.Exchange(ref State, 0);
Interlocked.Exchange(ref SetData, 0);
Message = "Cancelled";
OnDisplayExpired(true);
Interlocked.Exchange(ref _state, 0);
Interlocked.Exchange(ref _setData, 0);
Message = "Cancelled";
OnDisplayExpired(true);
}
}
private bool _isDisposed;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_isDisposed)
{
_isDisposed = true;
if (disposing)
{
_displayProgressTimer?.Dispose();
}
}
}
}
@@ -1,33 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<PropertyGroup Label="Compiler Properties">
<TargetFramework>net48</TargetFramework>
<LangVersion>10</LangVersion>
<RootNamespace>GrasshopperAsyncComponent</RootNamespace>
<AssemblyName>GrasshopperAsyncComponent</AssemblyName>
</PropertyGroup>
<PropertyGroup>
<PropertyGroup Label="Nugetspec Package Properties">
<PackageId>GrasshopperAsyncComponent</PackageId>
<Title>Grasshopper Async Component</Title>
<Description>Jankless Grasshopper Components</Description>
<Authors>Speckle Systems</Authors>
<Company>Speckle Systems</Company>
<Copyright>Copyright AEC Systems © 2020, 2021</Copyright>
<PackageTags>grasshopper rhino mcneel gh_component</PackageTags>
<ProjectGuid>{114D5E49-AC13-47F7-A70E-B4289579F4E3}</ProjectGuid>
</PropertyGroup>
<PropertyGroup>
<PackageId>GrasshopperAsyncComponent</PackageId>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/specklesystems/GrasshopperAsyncComponent</PackageProjectUrl>
<PackageTags>grasshopper rhino mcneel gh_component</PackageTags>
<PropertyGroup Label="Nuget Package Properties">
<IsPackable>true</IsPackable>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
<ItemGroup>
<ItemGroup Label="Package References">
<PackageReference Include="Grasshopper" Version="7.4.21078.1001" IncludeAssets="compile;build" PrivateAssets="all" />
</ItemGroup>
@@ -39,4 +31,4 @@
<StartProgram>C:\Program Files\Rhino 7\System\Rhino.exe</StartProgram>
<StartAction>Program</StartAction>
</PropertyGroup>
</Project>
</Project>
+23 -42
View File
@@ -1,66 +1,47 @@
using Grasshopper.Kernel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace GrasshopperAsyncComponent
namespace GrasshopperAsyncComponent;
/// <summary>
/// A class that holds the actual compute logic and encapsulates the state it needs. Every <see cref="GH_AsyncComponent"/> needs to have one.
/// </summary>
public abstract class WorkerInstance(GH_Component? parent, string id, CancellationToken cancellationToken)
{
/// <summary>
/// A class that holds the actual compute logic and encapsulates the state it needs. Every <see cref="GH_AsyncComponent"/> needs to have one.
/// </summary>
public abstract class WorkerInstance
{
/// <summary>
/// The parent component. Useful for passing state back to the host component.
/// </summary>
public GH_Component Parent { get; set; }
/// <summary>
/// This token is set by the parent <see cref="GH_AsyncComponent"/>.
/// </summary>
public CancellationToken CancellationToken { get; set; }
public GH_Component? Parent { get; set; } = parent;
/// <summary>
/// This is set by the parent <see cref="GH_AsyncComponent"/>. You can set it yourself, but it's not really worth it.
/// </summary>
public string Id { get; set; }
public CancellationToken CancellationToken { get; } = cancellationToken;
protected WorkerInstance(GH_Component _parent)
{
Parent = _parent;
}
public string Id { get; set; } = id;
/// <summary>
/// This is a "factory" method. It should return a fresh instance of this class, but with all the necessary state that you might have passed on directly from your component.
/// </summary>
/// <param name="id">A Unique id for the new duplicate</param>
/// <param name="cancellationToken">A cancellationToken to be passed to the new duplicate</param>
/// <returns></returns>
public abstract WorkerInstance Duplicate();
public abstract WorkerInstance? Duplicate(string id, CancellationToken cancellationToken);
/// <summary>
/// This method is where the actual calculation/computation/heavy lifting should be done.
/// <b>Make sure you always check as frequently as you can if <see cref="WorkerInstance.CancellationToken"/> is cancelled. For an example, see the <see cref="PrimeCalculatorWorker"/>.</b>
/// This method is where the actual calculation/computation/heavy lifting should be done.
/// <b>Make sure you always check as frequently as you can if <see cref="WorkerInstance.CancellationToken"/> is cancelled. For an example, see the PrimeCalculatorWorker example.</b>
/// </summary>
/// <param name="ReportProgress">Call this to report progress up to the parent component.</param>
/// <param name="Done">Call this when everything is <b>done</b>. It will tell the parent component that you're ready to <see cref="SetData(IGH_DataAccess)"/>.</param>
public abstract void DoWork(Action<string, double> ReportProgress, Action Done);
/// <param name="reportProgress">Call this to report progress up to the parent component.</param>
/// <param name="done">Call this when everything is <b>done</b>. It will tell the parent component that you're ready to <see cref="SetData(IGH_DataAccess)"/>.</param>
public abstract void DoWork(Action<string, double> reportProgress, Action done);
/// <summary>
/// Write your data setting logic here. <b>Do not call this function directly from this class. It will be invoked by the parent <see cref="GH_AsyncComponent"/> after you've called `Done` in the <see cref="DoWork(Action{string}, Action{string, GH_RuntimeMessageLevel}, Action)"/> function.</b>
/// Write your data setting logic here. <b>Do not call this function directly from this class. It will be invoked by the parent <see cref="GH_AsyncComponent"/> after you've called `Done` in the <see cref="DoWork"/> function.</b>
/// </summary>
/// <param name="DA"></param>
public abstract void SetData(IGH_DataAccess DA);
/// <param name="da"></param>
public abstract void SetData(IGH_DataAccess da);
/// <summary>
/// Write your data collection logic here. <b>Do not call this method directly. It will be invoked by the parent <see cref="GH_AsyncComponent"/>.</b>
/// </summary>
/// <param name="DA"></param>
/// <param name="Params"></param>
public abstract void GetData(IGH_DataAccess DA, GH_ComponentParamServer Params);
}
/// <param name="da"></param>
/// <param name="parameters"></param>
public abstract void GetData(IGH_DataAccess da, GH_ComponentParamServer parameters);
}
@@ -9,6 +9,7 @@
<RootNamespace>GrasshopperAsyncComponentDemo</RootNamespace>
<AssemblyName>GrasshopperAsyncComponentDemo</AssemblyName>
<GenerateResourceUsePreserializedResources>true</GenerateResourceUsePreserializedResources>
<NeutralLanguage>en</NeutralLanguage>
</PropertyGroup>
<ItemGroup>
@@ -28,4 +29,4 @@
<StartProgram>C:\Program Files\Rhino 7\System\Rhino.exe</StartProgram>
<StartAction>Program</StartAction>
</PropertyGroup>
</Project>
</Project>
@@ -1,57 +1,21 @@
using System;
using System.Drawing;
using Grasshopper.Kernel;
using Grasshopper.Kernel;
namespace GrasshopperAsyncComponentDemo
namespace GrasshopperAsyncComponentDemo;
public class GrasshopperAsyncComponentInfo : GH_AssemblyInfo
{
public class GrasshopperAsyncComponentInfo : GH_AssemblyInfo
{
public override string Name
{
get
{
return "GrasshopperAsyncComponentDemo";
}
}
public override Bitmap Icon
{
get
{
//Return a 24x24 pixel bitmap to represent this GHA library.
return null;
}
}
public override string Description
{
get
{
//Return a short string describing the purpose of this GHA library.
return "A base for async & less janky grasshopper components.";
}
}
public override Guid Id
{
get
{
return new Guid("9c8808bc-ddee-45ca-8c66-05ca3cf4d394");
}
}
public override string Name => "GrasshopperAsyncComponentDemo";
public override string AuthorName
{
get
{
//Return a string identifying you or your company.
return "";
}
}
public override string AuthorContact
{
get
{
//Return a string representing your preferred contact details.
return "";
}
}
}
//Return a short string describing the purpose of this GHA library.
public override string Description => "A base for async & less janky grasshopper components.";
public override Guid Id => new("9c8808bc-ddee-45ca-8c66-05ca3cf4d394");
//Return a string identifying you or your company.
public override string AuthorName => "";
//Return a string representing your preferred contact details.
public override string AuthorContact => "";
}
@@ -1,110 +1,143 @@
using Grasshopper.Kernel;
using System;
using System.Windows.Forms;
using System.Windows.Forms;
using Grasshopper.Kernel;
using GrasshopperAsyncComponent;
namespace GrasshopperAsyncComponentDemo.SampleImplementations
namespace GrasshopperAsyncComponentDemo.SampleImplementations;
public class Sample_PrimeCalculatorAsyncComponent : GH_AsyncComponent
{
public class Sample_PrimeCalculatorAsyncComponent : GH_AsyncComponent
public override Guid ComponentGuid => new Guid("22C612B0-2C57-47CE-B9FE-E10621F18933");
protected override System.Drawing.Bitmap Icon => Properties.Resources.logo32;
public override GH_Exposure Exposure => GH_Exposure.primary;
public Sample_PrimeCalculatorAsyncComponent()
: base("The N-th Prime Calculator", "PRIME", "Calculates the nth prime number.", "Samples", "Async")
{
public override Guid ComponentGuid { get => new Guid("22C612B0-2C57-47CE-B9FE-E10621F18933"); }
BaseWorker = new PrimeCalculatorWorker(this);
}
protected override System.Drawing.Bitmap Icon { get => Properties.Resources.logo32; }
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddIntegerParameter(
"N",
"N",
"Which n-th prime number. Minimum 1, maximum one million. Take care, it can burn your CPU.",
GH_ParamAccess.item
);
}
public override GH_Exposure Exposure => GH_Exposure.primary;
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddNumberParameter("Output", "O", "The n-th prime number.", GH_ParamAccess.item);
}
public Sample_PrimeCalculatorAsyncComponent() : base("The N-th Prime Calculator", "PRIME", "Calculates the nth prime number.", "Samples", "Async")
{
BaseWorker = new PrimeCalculatorWorker();
}
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddIntegerParameter("N", "N", "Which n-th prime number. Minimum 1, maximum one million. Take care, it can burn your CPU.", GH_ParamAccess.item);
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddNumberParameter("Output", "O", "The n-th prime number.", GH_ParamAccess.item);
}
public override void AppendAdditionalMenuItems(ToolStripDropDown menu)
{
base.AppendAdditionalMenuItems(menu);
Menu_AppendItem(menu, "Cancel", (s, e) =>
public override void AppendAdditionalMenuItems(ToolStripDropDown menu)
{
base.AppendAdditionalMenuItems(menu);
Menu_AppendItem(
menu,
"Cancel",
(s, e) =>
{
RequestCancellation();
});
}
}
);
}
private class PrimeCalculatorWorker : WorkerInstance
private sealed class PrimeCalculatorWorker : WorkerInstance
{
private int TheNthPrime { get; set; } = 100;
private long ThePrime { get; set; } = -1;
public PrimeCalculatorWorker(
GH_Component? parent,
string id = "baseworker",
CancellationToken cancellationToken = default
)
: base(parent, id, cancellationToken) { }
public override void DoWork(Action<string, double> reportProgress, Action done)
{
int TheNthPrime { get; set; } = 100;
long ThePrime { get; set; } = -1;
// 👉 Checking for cancellation!
if (CancellationToken.IsCancellationRequested)
{
return;
}
public PrimeCalculatorWorker() : base(null) { }
int count = 0;
long a = 2;
public override void DoWork(Action<string, double> ReportProgress, Action Done)
// Thanks Steak Overflow (TM) https://stackoverflow.com/a/13001749/
while (count < TheNthPrime)
{
// 👉 Checking for cancellation!
if (CancellationToken.IsCancellationRequested) { return; }
int count = 0;
long a = 2;
// Thanks Steak Overflow (TM) https://stackoverflow.com/a/13001749/
while (count < TheNthPrime)
if (CancellationToken.IsCancellationRequested)
{
// 👉 Checking for cancellation!
if (CancellationToken.IsCancellationRequested) { return; }
long b = 2;
int prime = 1;// to check if found a prime
while (b * b <= a)
{
// 👉 Checking for cancellation!
if (CancellationToken.IsCancellationRequested) { return; }
if (a % b == 0)
{
prime = 0;
break;
}
b++;
}
ReportProgress(Id, ((double)count) / TheNthPrime);
if (prime > 0)
{
count++;
}
a++;
return;
}
ThePrime = --a;
Done();
long b = 2;
int prime = 1; // to check if found a prime
while (b * b <= a)
{
// 👉 Checking for cancellation!
if (CancellationToken.IsCancellationRequested)
{
return;
}
if (a % b == 0)
{
prime = 0;
break;
}
b++;
}
reportProgress(Id, (double)count / TheNthPrime);
if (prime > 0)
{
count++;
}
a++;
}
public override WorkerInstance Duplicate() => new PrimeCalculatorWorker();
ThePrime = --a;
done();
}
public override void GetData(IGH_DataAccess DA, GH_ComponentParamServer Params)
public override WorkerInstance Duplicate(string id, CancellationToken cancellationToken) =>
new PrimeCalculatorWorker(Parent, id, cancellationToken);
public override void GetData(IGH_DataAccess da, GH_ComponentParamServer parameters)
{
int nthPrime = 100;
da.GetData(0, ref nthPrime);
if (nthPrime > 1000000)
{
int _nthPrime = 100;
DA.GetData(0, ref _nthPrime);
if (_nthPrime > 1000000) _nthPrime = 1000000;
if (_nthPrime < 1) _nthPrime = 1;
TheNthPrime = _nthPrime;
nthPrime = 1000000;
}
public override void SetData(IGH_DataAccess DA)
if (nthPrime < 1)
{
// 👉 Checking for cancellation!
if (CancellationToken.IsCancellationRequested) { return; }
DA.SetData(0, ThePrime);
nthPrime = 1;
}
TheNthPrime = nthPrime;
}
public override void SetData(IGH_DataAccess da)
{
// 👉 Checking for cancellation!
if (CancellationToken.IsCancellationRequested)
{
return;
}
da.SetData(0, ThePrime);
}
}
}
@@ -1,89 +1,115 @@
using Grasshopper.Kernel;
using System;
using System.Threading;
using System.Windows.Forms;
using System.Windows.Forms;
using Grasshopper.Kernel;
using GrasshopperAsyncComponent;
namespace GrasshopperAsyncComponentDemo.SampleImplementations;
namespace GrasshopperAsyncComponentDemo.SampleImplementations
public class Sample_UselessCyclesAsyncComponent : GH_AsyncComponent
{
public class Sample_UselessCyclesAsyncComponent : GH_AsyncComponent
public override Guid ComponentGuid => new Guid("DF2B93E2-052D-4BE4-BC62-90DC1F169BF6");
protected override System.Drawing.Bitmap Icon => Properties.Resources.logo32;
public override GH_Exposure Exposure => GH_Exposure.primary;
public Sample_UselessCyclesAsyncComponent()
: base("Sample Async Component", "CYCLOMATRON-X", "Spins uselessly.", "Samples", "Async")
{
public override Guid ComponentGuid { get => new Guid("DF2B93E2-052D-4BE4-BC62-90DC1F169BF6"); }
BaseWorker = new UselessCyclesWorker(this);
}
protected override System.Drawing.Bitmap Icon { get => Properties.Resources.logo32; }
private sealed class UselessCyclesWorker(
GH_Component? parent,
string id = "baseworker",
CancellationToken cancellationToken = default
) : WorkerInstance(parent, id, cancellationToken)
{
private int MaxIterations { get; set; } = 100;
public override GH_Exposure Exposure => GH_Exposure.primary;
public Sample_UselessCyclesAsyncComponent() : base("Sample Async Component", "CYCLOMATRON-X", "Spins uselessly.", "Samples", "Async")
public override void DoWork(Action<string, double> reportProgress, Action done)
{
BaseWorker = new UselessCyclesWorker();
}
private class UselessCyclesWorker : WorkerInstance
{
int MaxIterations { get; set; } = 100;
public UselessCyclesWorker() : base(null) { }
public override void DoWork(Action<string, double> ReportProgress, Action Done)
// Checking for cancellation
if (CancellationToken.IsCancellationRequested)
{
// Checking for cancellation
if (CancellationToken.IsCancellationRequested) { return; }
return;
}
for (int i = 0; i <= MaxIterations; i++)
for (int i = 0; i <= MaxIterations; i++)
{
var sw = new SpinWait();
for (int j = 0; j <= 100; j++)
{
var sw = new SpinWait();
for (int j = 0; j <= 100; j++)
sw.SpinOnce();
ReportProgress(Id, ((double)(i + 1) / (double)MaxIterations));
// Checking for cancellation
if (CancellationToken.IsCancellationRequested) { return; }
sw.SpinOnce();
}
Done();
reportProgress(Id, (i + 1) / (double)MaxIterations);
// Checking for cancellation
if (CancellationToken.IsCancellationRequested)
{
return;
}
}
public override WorkerInstance Duplicate() => new UselessCyclesWorker();
done();
}
public override void GetData(IGH_DataAccess DA, GH_ComponentParamServer Params)
public override WorkerInstance Duplicate(string id, CancellationToken cancellationToken) =>
new UselessCyclesWorker(Parent, id, cancellationToken);
public override void GetData(IGH_DataAccess da, GH_ComponentParamServer parameters)
{
if (CancellationToken.IsCancellationRequested)
{
if (CancellationToken.IsCancellationRequested) return;
int _maxIterations = 100;
DA.GetData(0, ref _maxIterations);
if (_maxIterations > 1000) _maxIterations = 1000;
if (_maxIterations < 10) _maxIterations = 10;
MaxIterations = _maxIterations;
return;
}
public override void SetData(IGH_DataAccess DA)
int maxIterations = 100;
da.GetData(0, ref maxIterations);
if (maxIterations > 1000)
{
if (CancellationToken.IsCancellationRequested) return;
DA.SetData(0, $"Hello world. Worker {Id} has spun for {MaxIterations} iterations.");
maxIterations = 1000;
}
}
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddIntegerParameter("N", "N", "Number of spins.", GH_ParamAccess.item);
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddTextParameter("Output", "O", "Nothing really interesting.", GH_ParamAccess.item);
}
public override void AppendAdditionalMenuItems(ToolStripDropDown menu)
{
base.AppendAdditionalMenuItems(menu);
Menu_AppendItem(menu, "Cancel", (s, e) =>
if (maxIterations < 10)
{
RequestCancellation();
});
maxIterations = 10;
}
MaxIterations = maxIterations;
}
public override void SetData(IGH_DataAccess da)
{
if (CancellationToken.IsCancellationRequested)
{
return;
}
da.SetData(0, $"Hello world. Worker {Id} has spun for {MaxIterations} iterations.");
}
}
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddIntegerParameter("N", "N", "Number of spins.", GH_ParamAccess.item);
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddTextParameter("Output", "O", "Nothing really interesting.", GH_ParamAccess.item);
}
public override void AppendAdditionalMenuItems(ToolStripDropDown menu)
{
base.AppendAdditionalMenuItems(menu);
Menu_AppendItem(
menu,
"Cancel",
(s, e) =>
{
RequestCancellation();
}
);
}
}