From eb97eb1df8bb1ecc8660b8507b8ad43e08648519 Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Fri, 13 Jun 2025 17:29:55 +0100 Subject: [PATCH] refactor: Add sourcelink and Github Actions (#28) * Quick pass * no locks * github actions * publish via github actions * pack --- .circleci/config.yml | 52 --- .csharpierignore | 26 ++ .csharpierrc.yaml | 7 + .editorconfig | 327 ++++++++++++++++ .github/workflows/close-issue.yml | 77 ---- .github/workflows/open-issue.yml | 50 --- .github/workflows/open-ssie.yml | 50 --- .github/workflows/workflow.yml | 49 +++ CodeMetricsConfig.txt | 4 + Directory.Build.props | 69 ++++ GrasshopperAsyncComponent.sln | 8 + .../GH_AsyncComponent.cs | 360 +++++++++--------- .../GrasshopperAsyncComponent.csproj | 28 +- GrasshopperAsyncComponent/WorkerInstance.cs | 65 ++-- .../GrasshopperAsyncComponentDemo.csproj | 3 +- .../Info/GrasshopperAsyncComponentInfo.cs | 70 +--- .../Sample_PrimeCalculatorAsyncComponent.cs | 195 ++++++---- .../Sample_UslessCyclesComponent.cs | 152 +++++--- 18 files changed, 934 insertions(+), 658 deletions(-) delete mode 100755 .circleci/config.yml create mode 100644 .csharpierignore create mode 100644 .csharpierrc.yaml create mode 100644 .editorconfig delete mode 100644 .github/workflows/close-issue.yml delete mode 100644 .github/workflows/open-issue.yml delete mode 100644 .github/workflows/open-ssie.yml create mode 100644 .github/workflows/workflow.yml create mode 100644 CodeMetricsConfig.txt create mode 100644 Directory.Build.props diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100755 index 96caae0..0000000 --- a/.circleci/config.yml +++ /dev/null @@ -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 diff --git a/.csharpierignore b/.csharpierignore new file mode 100644 index 0000000..072d074 --- /dev/null +++ b/.csharpierignore @@ -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.* \ No newline at end of file diff --git a/.csharpierrc.yaml b/.csharpierrc.yaml new file mode 100644 index 0000000..b8bb425 --- /dev/null +++ b/.csharpierrc.yaml @@ -0,0 +1,7 @@ +printWidth: 120 +useTabs: false +indentSize: 2 +preprocessorSymbolSets: + - "" + - "DEBUG" + - "DEBUG,CODE_STYLE" diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..27feb2d --- /dev/null +++ b/.editorconfig @@ -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 \ No newline at end of file diff --git a/.github/workflows/close-issue.yml b/.github/workflows/close-issue.yml deleted file mode 100644 index 21a1d7a..0000000 --- a/.github/workflows/close-issue.yml +++ /dev/null @@ -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 }} diff --git a/.github/workflows/open-issue.yml b/.github/workflows/open-issue.yml deleted file mode 100644 index 0c0943c..0000000 --- a/.github/workflows/open-issue.yml +++ /dev/null @@ -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 diff --git a/.github/workflows/open-ssie.yml b/.github/workflows/open-ssie.yml deleted file mode 100644 index 831d2b0..0000000 --- a/.github/workflows/open-ssie.yml +++ /dev/null @@ -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 diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml new file mode 100644 index 0000000..ffb0e48 --- /dev/null +++ b/.github/workflows/workflow.yml @@ -0,0 +1,49 @@ +name: .NET Build and Publish + +on: + pull_request: + push: + tags: ["v*.*.*"] + +jobs: + build: + env: + SOLUTION_NAME: "GrasshopperAsyncComponent.sln" + 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 output/*.nupkg --source "https://api.nuget.org/v3/index.json" --api-key ${{secrets.CONNECTORS_NUGET_TOKEN }} --skip-duplicate diff --git a/CodeMetricsConfig.txt b/CodeMetricsConfig.txt new file mode 100644 index 0000000..e69bf23 --- /dev/null +++ b/CodeMetricsConfig.txt @@ -0,0 +1,4 @@ +CA1502: 25 +CA1501: 5 +CA1506(Method): 50 +CA1506(Type): 95 diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..b1b69d9 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,69 @@ + + + 12 + enable + enable + + + + + + Speckle + Copyright (c) AEC Systems Ltd + https://speckle.systems/ + README.md + https://github.com/specklesystems/GrasshopperAsyncComponent + git + speckle + Apache-2.0 + + + false + + true + true + $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb + + + true + + + true + latest-AllEnabledByDefault + true + true + false + true + + + + CA5399;CA1812; + + CS1591;CS1573; + + CA1303;CA1304;CA1305;CA1307;CA1308;CA1309;CA1310;CA1311; + + CA1848;CA1727; + + CA1815;CA1725; + + CA1710;CA1711;CA1720;CA1724; + + CA1502;CA1716;NETSDK1206; + $(NoWarn) + + + + + $(MSBuildThisFileDirectory) + + + + + + + + + diff --git a/GrasshopperAsyncComponent.sln b/GrasshopperAsyncComponent.sln index 6d573ca..293d009 100644 --- a/GrasshopperAsyncComponent.sln +++ b/GrasshopperAsyncComponent.sln @@ -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 diff --git a/GrasshopperAsyncComponent/GH_AsyncComponent.cs b/GrasshopperAsyncComponent/GH_AsyncComponent.cs index 3178cd3..60a6681 100755 --- a/GrasshopperAsyncComponent/GH_AsyncComponent.cs +++ b/GrasshopperAsyncComponent/GH_AsyncComponent.cs @@ -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 -{ - /// - /// Inherit your component from this class to make all the async goodness available. - /// - 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; +/// +/// Inherit your component from this class to make all the async goodness available. +/// +public abstract class GH_AsyncComponent : GH_Component, IDisposable +{ //List<(string, GH_RuntimeMessageLevel)> Errors; - Action ReportProgress; + private readonly Action _reportProgress; - public ConcurrentDictionary ProgressReports; + public ConcurrentDictionary 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 Workers; + public List Workers { get; protected set; } - List Tasks; + private readonly List _tasks; - public readonly List CancellationSources; + public List CancellationSources { get; } /// - /// Set this property inside the constructor of your derived component. + /// Set this property inside the constructor of your derived component. /// - public WorkerInstance BaseWorker { get; set; } + public WorkerInstance? BaseWorker { get; set; } /// /// Optional: if you have opinions on how the default system task scheduler should treat your workers, set it here. /// - 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(); + CancellationSources = new List(); + _tasks = new List(); + ProgressReports = new ConcurrentDictionary(); - 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(); - - Workers = new List(); - CancellationSources = new List(); - Tasks = new List(); + 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(); + } + } + } } diff --git a/GrasshopperAsyncComponent/GrasshopperAsyncComponent.csproj b/GrasshopperAsyncComponent/GrasshopperAsyncComponent.csproj index df8c1ba..21e9a93 100755 --- a/GrasshopperAsyncComponent/GrasshopperAsyncComponent.csproj +++ b/GrasshopperAsyncComponent/GrasshopperAsyncComponent.csproj @@ -1,33 +1,25 @@ - + net48 - 10 GrasshopperAsyncComponent GrasshopperAsyncComponent - - + + + GrasshopperAsyncComponent Grasshopper Async Component Jankless Grasshopper Components - Speckle Systems - Speckle Systems - Copyright AEC Systems © 2020, 2021 + grasshopper rhino mcneel gh_component {114D5E49-AC13-47F7-A70E-B4289579F4E3} - - - GrasshopperAsyncComponent - false - Apache-2.0 - https://github.com/specklesystems/GrasshopperAsyncComponent - grasshopper rhino mcneel gh_component + + true true snupkg - true - - + + @@ -39,4 +31,4 @@ C:\Program Files\Rhino 7\System\Rhino.exe Program - \ No newline at end of file + diff --git a/GrasshopperAsyncComponent/WorkerInstance.cs b/GrasshopperAsyncComponent/WorkerInstance.cs index e633c93..cd21e0c 100755 --- a/GrasshopperAsyncComponent/WorkerInstance.cs +++ b/GrasshopperAsyncComponent/WorkerInstance.cs @@ -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; + +/// +/// A class that holds the actual compute logic and encapsulates the state it needs. Every needs to have one. +/// +public abstract class WorkerInstance(GH_Component? parent, string id, CancellationToken cancellationToken) { - - /// - /// A class that holds the actual compute logic and encapsulates the state it needs. Every needs to have one. - /// - public abstract class WorkerInstance - { - /// /// The parent component. Useful for passing state back to the host component. /// - public GH_Component Parent { get; set; } - - /// - /// This token is set by the parent . - /// - public CancellationToken CancellationToken { get; set; } + public GH_Component? Parent { get; set; } = parent; - /// - /// This is set by the parent . You can set it yourself, but it's not really worth it. - /// - public string Id { get; set; } + public CancellationToken CancellationToken { get; } = cancellationToken; - protected WorkerInstance(GH_Component _parent) - { - Parent = _parent; - } + public string Id { get; set; } = id; /// /// 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. /// + /// A Unique id for the new duplicate + /// A cancellationToken to be passed to the new duplicate /// - public abstract WorkerInstance Duplicate(); + public abstract WorkerInstance? Duplicate(string id, CancellationToken cancellationToken); /// - /// This method is where the actual calculation/computation/heavy lifting should be done. - /// Make sure you always check as frequently as you can if is cancelled. For an example, see the . + /// This method is where the actual calculation/computation/heavy lifting should be done. + /// Make sure you always check as frequently as you can if is cancelled. For an example, see the PrimeCalculatorWorker example. /// - /// Call this to report progress up to the parent component. - /// Call this when everything is done. It will tell the parent component that you're ready to . - public abstract void DoWork(Action ReportProgress, Action Done); + /// Call this to report progress up to the parent component. + /// Call this when everything is done. It will tell the parent component that you're ready to . + public abstract void DoWork(Action reportProgress, Action done); /// - /// Write your data setting logic here. Do not call this function directly from this class. It will be invoked by the parent after you've called `Done` in the function. + /// Write your data setting logic here. Do not call this function directly from this class. It will be invoked by the parent after you've called `Done` in the function. /// - /// - public abstract void SetData(IGH_DataAccess DA); + /// + public abstract void SetData(IGH_DataAccess da); /// /// Write your data collection logic here. Do not call this method directly. It will be invoked by the parent . /// - /// - /// - public abstract void GetData(IGH_DataAccess DA, GH_ComponentParamServer Params); - } - + /// + /// + public abstract void GetData(IGH_DataAccess da, GH_ComponentParamServer parameters); } diff --git a/GrasshopperAsyncComponentDemo/GrasshopperAsyncComponentDemo.csproj b/GrasshopperAsyncComponentDemo/GrasshopperAsyncComponentDemo.csproj index 50bf0c3..23a994e 100644 --- a/GrasshopperAsyncComponentDemo/GrasshopperAsyncComponentDemo.csproj +++ b/GrasshopperAsyncComponentDemo/GrasshopperAsyncComponentDemo.csproj @@ -9,6 +9,7 @@ GrasshopperAsyncComponentDemo GrasshopperAsyncComponentDemo true + en @@ -28,4 +29,4 @@ C:\Program Files\Rhino 7\System\Rhino.exe Program - \ No newline at end of file + diff --git a/GrasshopperAsyncComponentDemo/Info/GrasshopperAsyncComponentInfo.cs b/GrasshopperAsyncComponentDemo/Info/GrasshopperAsyncComponentInfo.cs index c69295a..a3dda17 100644 --- a/GrasshopperAsyncComponentDemo/Info/GrasshopperAsyncComponentInfo.cs +++ b/GrasshopperAsyncComponentDemo/Info/GrasshopperAsyncComponentInfo.cs @@ -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 => ""; } diff --git a/GrasshopperAsyncComponentDemo/SampleImplementations/Sample_PrimeCalculatorAsyncComponent.cs b/GrasshopperAsyncComponentDemo/SampleImplementations/Sample_PrimeCalculatorAsyncComponent.cs index cb79606..e1bacb0 100755 --- a/GrasshopperAsyncComponentDemo/SampleImplementations/Sample_PrimeCalculatorAsyncComponent.cs +++ b/GrasshopperAsyncComponentDemo/SampleImplementations/Sample_PrimeCalculatorAsyncComponent.cs @@ -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 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 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); } } } diff --git a/GrasshopperAsyncComponentDemo/SampleImplementations/Sample_UslessCyclesComponent.cs b/GrasshopperAsyncComponentDemo/SampleImplementations/Sample_UslessCyclesComponent.cs index bccce9f..6fc0466 100755 --- a/GrasshopperAsyncComponentDemo/SampleImplementations/Sample_UslessCyclesComponent.cs +++ b/GrasshopperAsyncComponentDemo/SampleImplementations/Sample_UslessCyclesComponent.cs @@ -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 reportProgress, Action done) { - BaseWorker = new UselessCyclesWorker(); - } - - private class UselessCyclesWorker : WorkerInstance - { - int MaxIterations { get; set; } = 100; - - public UselessCyclesWorker() : base(null) { } - - public override void DoWork(Action 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(); + } + ); + } }