From 939890aa3c1a79e48d159c4b724d95e6cb7b057a Mon Sep 17 00:00:00 2001 From: FireController#1847 Date: Sat, 25 Oct 2025 13:05:07 -0600 Subject: [PATCH 01/20] Add the core domain interfaces * Imported the primary domain interfaces from previous project iterations. * Resolved issue where ILayer did not condition TDomain to extend from IDomain. * Added a latest build shield to the README.md * Imported the .editorconfig from previous project iterations. --- CatalystUI/.editorconfig | 300 ++++++++++++++++++ .../CatalystUI.Core/CatalystUI.Core.csproj | 3 - .../Domains/IAuditoryDomain.cs | 23 ++ .../Domains/IGustatoryDomain.cs | 23 ++ .../Domains/IMultisensoryDomain.cs | 23 ++ .../Domains/IOlfactoryDomain.cs | 23 ++ .../Domains/ISymbolicDomain.cs | 23 ++ .../CatalystUI.Core/Domains/ITactileDomain.cs | 23 ++ .../CatalystUI.Core/Domains/IVisualDomain.cs | 23 ++ CatalystUI/Core/CatalystUI.Core/IConnector.cs | 40 +++ CatalystUI/Core/CatalystUI.Core/IDomain.cs | 24 ++ CatalystUI/Core/CatalystUI.Core/ILayer.cs | 32 ++ README.md | 2 +- 13 files changed, 558 insertions(+), 4 deletions(-) create mode 100644 CatalystUI/.editorconfig create mode 100644 CatalystUI/Core/CatalystUI.Core/Domains/IAuditoryDomain.cs create mode 100644 CatalystUI/Core/CatalystUI.Core/Domains/IGustatoryDomain.cs create mode 100644 CatalystUI/Core/CatalystUI.Core/Domains/IMultisensoryDomain.cs create mode 100644 CatalystUI/Core/CatalystUI.Core/Domains/IOlfactoryDomain.cs create mode 100644 CatalystUI/Core/CatalystUI.Core/Domains/ISymbolicDomain.cs create mode 100644 CatalystUI/Core/CatalystUI.Core/Domains/ITactileDomain.cs create mode 100644 CatalystUI/Core/CatalystUI.Core/Domains/IVisualDomain.cs create mode 100644 CatalystUI/Core/CatalystUI.Core/IConnector.cs create mode 100644 CatalystUI/Core/CatalystUI.Core/IDomain.cs create mode 100644 CatalystUI/Core/CatalystUI.Core/ILayer.cs diff --git a/CatalystUI/.editorconfig b/CatalystUI/.editorconfig new file mode 100644 index 0000000..355e106 --- /dev/null +++ b/CatalystUI/.editorconfig @@ -0,0 +1,300 @@ +root = true + +# C# files +[*.cs] + +#### Core EditorConfig Options #### + +# Indentation and spacing +indent_size = 4 +indent_style = space +tab_width = 4 + +# New line preferences +end_of_line = crlf +insert_final_newline = false + +#### .NET Code Actions #### + +# Type members +dotnet_hide_advanced_members = false +dotnet_member_insertion_location = with_other_members_of_the_same_kind +dotnet_property_generation_behavior = prefer_throwing_properties + +# Symbol search +dotnet_search_reference_assemblies = true + +#### .NET Coding Conventions #### + +# Organize usings +dotnet_separate_import_directive_groups = false +dotnet_sort_system_directives_first = false +file_header_template = -------------------------------------------------------------------------------------------------\nCatalystUI Framework for .NET Core - https://catalystui.org/\nCopyright (c) 2025 CatalystUI LLC. All rights reserved.\n\nThis file is part of CatalystUI and is provided as part of an early-access release.\nUnauthorized commercial use, distribution, or modification is strictly prohibited.\n\nThis software is not open source and is not publicly licensed.\nFor full terms, see the LICENSE and NOTICE files in the project root.\n------------------------------------------------------------------------------------------------- + +# this. and Me. preferences +dotnet_style_qualification_for_event = false +dotnet_style_qualification_for_field = false +dotnet_style_qualification_for_method = false +dotnet_style_qualification_for_property = false + +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true +dotnet_style_predefined_type_for_member_access = true + +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_operators = never_if_unnecessary +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity + +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members + +# Expression-level preferences +dotnet_prefer_system_hash_code = true +dotnet_style_coalesce_expression = true +dotnet_style_collection_initializer = true +dotnet_style_explicit_tuple_names = true +dotnet_style_namespace_match_folder = true +dotnet_style_null_propagation = true +dotnet_style_object_initializer = true +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_prefer_auto_properties = true +dotnet_style_prefer_collection_expression = when_types_loosely_match +dotnet_style_prefer_compound_assignment = true +dotnet_style_prefer_conditional_expression_over_assignment = true +dotnet_style_prefer_conditional_expression_over_return = true +dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed +dotnet_style_prefer_inferred_anonymous_type_member_names = true +dotnet_style_prefer_inferred_tuple_names = true +dotnet_style_prefer_is_null_check_over_reference_equality_method = true +dotnet_style_prefer_simplified_boolean_expressions = true +dotnet_style_prefer_simplified_interpolation = true + +# Field preferences +dotnet_style_readonly_field = true + +# Parameter preferences +dotnet_code_quality_unused_parameters = all + +# Suppression preferences +dotnet_remove_unnecessary_suppression_exclusions = none + +# New line preferences +dotnet_style_allow_multiple_blank_lines_experimental = true +dotnet_style_allow_statement_immediately_after_block_experimental = true + +#### C# Coding Conventions #### + +# var preferences +csharp_style_var_elsewhere = false +csharp_style_var_for_built_in_types = false +csharp_style_var_when_type_is_apparent = false + +# Expression-bodied members +csharp_style_expression_bodied_accessors = true +csharp_style_expression_bodied_constructors = false +csharp_style_expression_bodied_indexers = true +csharp_style_expression_bodied_lambdas = true +csharp_style_expression_bodied_local_functions = false +csharp_style_expression_bodied_methods = false +csharp_style_expression_bodied_operators = false +csharp_style_expression_bodied_properties = true + +# Pattern matching preferences +csharp_style_pattern_matching_over_as_with_null_check = true +csharp_style_pattern_matching_over_is_with_cast_check = true +csharp_style_prefer_extended_property_pattern = true +csharp_style_prefer_not_pattern = true +csharp_style_prefer_pattern_matching = true +csharp_style_prefer_switch_expression = true + +# Null-checking preferences +csharp_style_conditional_delegate_call = true + +# Modifier preferences +csharp_prefer_static_anonymous_function = true +csharp_prefer_static_local_function = true +csharp_preferred_modifier_order = public, private, protected, internal, file, static, extern, new, virtual, abstract, sealed, override, readonly, unsafe, required, volatile, async +csharp_style_prefer_readonly_struct = true +csharp_style_prefer_readonly_struct_member = true + +# Code-block preferences +csharp_prefer_braces = true +csharp_prefer_simple_using_statement = true +csharp_prefer_system_threading_lock = true +csharp_style_namespace_declarations = block_scoped +csharp_style_prefer_method_group_conversion = true +csharp_style_prefer_primary_constructors = true +csharp_style_prefer_top_level_statements = true + +# Expression-level preferences +csharp_prefer_simple_default_expression = true +csharp_style_deconstructed_variable_declaration = true +csharp_style_implicit_object_creation_when_type_is_apparent = true +csharp_style_inlined_variable_declaration = true +csharp_style_prefer_implicitly_typed_lambda_expression = true +csharp_style_prefer_index_operator = true +csharp_style_prefer_local_over_anonymous_function = true +csharp_style_prefer_null_check_over_type_check = true +csharp_style_prefer_range_operator = true +csharp_style_prefer_tuple_swap = true +csharp_style_prefer_unbound_generic_type_in_nameof = true +csharp_style_prefer_utf8_string_literals = true +csharp_style_throw_expression = true +csharp_style_unused_value_assignment_preference = discard_variable +csharp_style_unused_value_expression_statement_preference = discard_variable + +# 'using' directive preferences +csharp_using_directive_placement = outside_namespace + +# New line preferences +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true +csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true +csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true +csharp_style_allow_embedded_statements_on_same_line_experimental = true + +#### C# Formatting Rules #### + +# New line preferences +csharp_new_line_before_catch = false +csharp_new_line_before_else = false +csharp_new_line_before_finally = false +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_open_brace = none +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true + +# Space preferences +csharp_space_after_cast = true +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# Wrapping preferences +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Naming styles + +dotnet_naming_style.all_upper.required_prefix = +dotnet_naming_style.all_upper.required_suffix = +dotnet_naming_style.all_upper.word_separator = _ +dotnet_naming_style.all_upper.capitalization = all_upper + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + +dotnet_naming_style.begins_with_underscore.required_prefix = _ +dotnet_naming_style.begins_with_underscore.required_suffix = +dotnet_naming_style.begins_with_underscore.word_separator = +dotnet_naming_style.begins_with_underscore.capitalization = camel_case + +# Naming rule for constant fields +dotnet_naming_symbols.static_readonly_fields.applicable_kinds = field +dotnet_naming_symbols.static_readonly_fields.applicable_accessibilities = public +dotnet_naming_symbols.static_readonly_fields.required_modifiers = static, readonly +dotnet_naming_rule.static_readonly_fields_should_be_upper_case.severity = suggestion +dotnet_naming_rule.static_readonly_fields_should_be_upper_case.symbols = static_readonly_fields +dotnet_naming_rule.static_readonly_fields_should_be_upper_case.style = all_upper +dotnet_naming_symbols.const_fields.applicable_kinds = field +dotnet_naming_symbols.const_fields.applicable_accessibilities = public +dotnet_naming_symbols.const_fields.required_modifiers = const +dotnet_naming_rule.const_fields_should_be_upper_case.severity = suggestion +dotnet_naming_rule.const_fields_should_be_upper_case.symbols = const_fields +dotnet_naming_rule.const_fields_should_be_upper_case.style = all_upper + +# Naming correction for enum members (only supports ReSharper; see https://github.com/dotnet/roslyn/issues/24209) +resharper_csharp_naming_rule.enum_member = AaBb + +# Naming rule for instance fields +dotnet_naming_symbols.private_fields.applicable_kinds = field +dotnet_naming_symbols.private_fields.applicable_accessibilities = * +dotnet_naming_symbols.private_fields.required_modifiers = +dotnet_naming_rule.private_fields_should_be_begins_with_underscore.severity = suggestion +dotnet_naming_rule.private_fields_should_be_begins_with_underscore.symbols = private_fields +dotnet_naming_rule.private_fields_should_be_begins_with_underscore.style = begins_with_underscore + +# ReSharper properties +resharper_blank_lines_after_multiline_statements = 0 +resharper_blank_lines_after_start_comment = 1 +resharper_blank_lines_after_using_list = 1 +resharper_blank_lines_around_auto_property = 1 +resharper_blank_lines_around_field = 1 +resharper_blank_lines_around_invocable = 1 +resharper_blank_lines_around_namespace = 1 +resharper_blank_lines_around_property = 1 +resharper_blank_lines_around_region = 1 +resharper_blank_lines_around_single_line_type = 0 +resharper_blank_lines_around_type = 1 +resharper_blank_lines_before_control_transfer_statements = 0 +resharper_blank_lines_inside_namespace = 1 +resharper_blank_lines_inside_type = 1 +resharper_csharp_remove_spaces_on_blank_lines = false +resharper_space_within_array_access_brackets = false +resharper_space_within_array_rank_brackets = false +resharper_space_within_list_pattern_brackets = true \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Core/CatalystUI.Core.csproj b/CatalystUI/Core/CatalystUI.Core/CatalystUI.Core.csproj index 0621225..7676c3e 100644 --- a/CatalystUI/Core/CatalystUI.Core/CatalystUI.Core.csproj +++ b/CatalystUI/Core/CatalystUI.Core/CatalystUI.Core.csproj @@ -16,8 +16,5 @@ - - - \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Core/Domains/IAuditoryDomain.cs b/CatalystUI/Core/CatalystUI.Core/Domains/IAuditoryDomain.cs new file mode 100644 index 0000000..6039fe7 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/Domains/IAuditoryDomain.cs @@ -0,0 +1,23 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +namespace Catalyst.Domains { + + /// + /// Represents the auditory domain in the CatalystUI model. + /// + public interface IAuditoryDomain : IDomain { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Core/Domains/IGustatoryDomain.cs b/CatalystUI/Core/CatalystUI.Core/Domains/IGustatoryDomain.cs new file mode 100644 index 0000000..a62b5e4 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/Domains/IGustatoryDomain.cs @@ -0,0 +1,23 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +namespace Catalyst.Domains { + + /// + /// Represents the gustatory domain in the CatalystUI model. + /// + public interface IGustatoryDomain : IDomain { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Core/Domains/IMultisensoryDomain.cs b/CatalystUI/Core/CatalystUI.Core/Domains/IMultisensoryDomain.cs new file mode 100644 index 0000000..673c826 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/Domains/IMultisensoryDomain.cs @@ -0,0 +1,23 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +namespace Catalyst.Domains { + + /// + /// Represents the multisensory domain in the CatalystUI model. + /// + public interface IMultisensoryDomain : IDomain { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Core/Domains/IOlfactoryDomain.cs b/CatalystUI/Core/CatalystUI.Core/Domains/IOlfactoryDomain.cs new file mode 100644 index 0000000..e9af3f2 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/Domains/IOlfactoryDomain.cs @@ -0,0 +1,23 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +namespace Catalyst.Domains { + + /// + /// Represents the olfactory domain in the CatalystUI model. + /// + public interface IOlfactoryDomain : IDomain { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Core/Domains/ISymbolicDomain.cs b/CatalystUI/Core/CatalystUI.Core/Domains/ISymbolicDomain.cs new file mode 100644 index 0000000..9b0ba52 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/Domains/ISymbolicDomain.cs @@ -0,0 +1,23 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +namespace Catalyst.Domains { + + /// + /// Represents the symbolic domain in the CatalystUI model. + /// + public interface ISymbolicDomain : IDomain { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Core/Domains/ITactileDomain.cs b/CatalystUI/Core/CatalystUI.Core/Domains/ITactileDomain.cs new file mode 100644 index 0000000..408c636 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/Domains/ITactileDomain.cs @@ -0,0 +1,23 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +namespace Catalyst.Domains { + + /// + /// Represents the tactile domain in the CatalystUI model. + /// + public interface ITactileDomain : IDomain { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Core/Domains/IVisualDomain.cs b/CatalystUI/Core/CatalystUI.Core/Domains/IVisualDomain.cs new file mode 100644 index 0000000..c7f657a --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/Domains/IVisualDomain.cs @@ -0,0 +1,23 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +namespace Catalyst.Domains { + + /// + /// Represents the visual domain in the CatalystUI model. + /// + public interface IVisualDomain : IDomain { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Core/IConnector.cs b/CatalystUI/Core/CatalystUI.Core/IConnector.cs new file mode 100644 index 0000000..8fd590d --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/IConnector.cs @@ -0,0 +1,40 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Domains; +using Catalyst.Layers; +using System; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Connectors { + + /// + /// Represents a connector in the CatalystUI model. + /// + /// The layer type associated with the lower-level abstraction. + /// The layer type associated with the higher-level abstraction. + public interface IConnector where TLayerLow : ILayer where TLayerHigh : ILayer { + + /// + /// Gets the type of the higher layer of the connector. + /// + /// The connector's high layer type. + static virtual Type HighLayer => typeof(TLayerHigh); + + /// + /// Gets the type of the lower layer of the connector. + /// + /// The connector's low layer type. + static virtual Type LowLayer => typeof(TLayerLow); + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Core/IDomain.cs b/CatalystUI/Core/CatalystUI.Core/IDomain.cs new file mode 100644 index 0000000..669a223 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/IDomain.cs @@ -0,0 +1,24 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +// ReSharper disable once CheckNamespace +namespace Catalyst.Domains { + + /// + /// Represents a domain in the CatalystUI model. + /// + public interface IDomain { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Core/ILayer.cs b/CatalystUI/Core/CatalystUI.Core/ILayer.cs new file mode 100644 index 0000000..fa43891 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/ILayer.cs @@ -0,0 +1,32 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Domains; +using System; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Layers { + + /// + /// Represents a layer in the CatalystUI model. + /// + /// The domain type associated with the layer. + public interface ILayer where TDomain : IDomain { + + /// + /// Gets the type of the domain of the layer. + /// + /// The layer's domain type. + static virtual Type Domain => typeof(TDomain); + + } + +} \ No newline at end of file diff --git a/README.md b/README.md index f107bde..732c1ec 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# CatalystUI Framework for .NET Core ![Static Badge](https://img.shields.io/badge/Powered_by-.NET-blue?style=flat-square&logo=sharp&logoColor=%23ffffff) +# CatalystUI Framework for .NET Core ![Static Badge](https://img.shields.io/badge/Powered_by-.NET-blue?style=flat-square&logo=sharp&logoColor=%23ffffff) ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/CatalystUI/NetCore/dotnet.yml?branch=main&style=flat-square) A lightweight implementation of the CatalystUI Model for .NET Core. From 49ddb69512f1b0630de68127c1e0412630267a5d Mon Sep 17 00:00:00 2001 From: FireController#1847 Date: Sat, 25 Oct 2025 13:07:57 -0600 Subject: [PATCH 02/20] Add the core layer interfaces * Imported the primary layer interfaces from previous project iterations. --- .../Layers/IComponentsLayer.cs | 27 +++++++++++++++++++ .../Core/CatalystUI.Core/Layers/IDataLayer.cs | 27 +++++++++++++++++++ .../CatalystUI.Core/Layers/IFrameLayer.cs | 27 +++++++++++++++++++ .../CatalystUI.Core/Layers/IRendererLayer.cs | 26 ++++++++++++++++++ .../CatalystUI.Core/Layers/ISemanticsLayer.cs | 26 ++++++++++++++++++ .../CatalystUI.Core/Layers/ISystemLayer.cs | 26 ++++++++++++++++++ .../CatalystUI.Core/Layers/IWindowLayer.cs | 26 ++++++++++++++++++ 7 files changed, 185 insertions(+) create mode 100644 CatalystUI/Core/CatalystUI.Core/Layers/IComponentsLayer.cs create mode 100644 CatalystUI/Core/CatalystUI.Core/Layers/IDataLayer.cs create mode 100644 CatalystUI/Core/CatalystUI.Core/Layers/IFrameLayer.cs create mode 100644 CatalystUI/Core/CatalystUI.Core/Layers/IRendererLayer.cs create mode 100644 CatalystUI/Core/CatalystUI.Core/Layers/ISemanticsLayer.cs create mode 100644 CatalystUI/Core/CatalystUI.Core/Layers/ISystemLayer.cs create mode 100644 CatalystUI/Core/CatalystUI.Core/Layers/IWindowLayer.cs diff --git a/CatalystUI/Core/CatalystUI.Core/Layers/IComponentsLayer.cs b/CatalystUI/Core/CatalystUI.Core/Layers/IComponentsLayer.cs new file mode 100644 index 0000000..d0a619a --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/Layers/IComponentsLayer.cs @@ -0,0 +1,27 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + + +using Catalyst.Domains; + +namespace Catalyst.Layers { + + /// + /// Represents the components layer in the CatalystUI model. + /// + /// + public interface IComponentsLayer : ILayer where TDomain : IDomain { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Core/Layers/IDataLayer.cs b/CatalystUI/Core/CatalystUI.Core/Layers/IDataLayer.cs new file mode 100644 index 0000000..fc07364 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/Layers/IDataLayer.cs @@ -0,0 +1,27 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + + +using Catalyst.Domains; + +namespace Catalyst.Layers { + + /// + /// Represents the data layer in the CatalystUI model. + /// + /// + public interface IDataLayer : ILayer where TDomain : IDomain { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Core/Layers/IFrameLayer.cs b/CatalystUI/Core/CatalystUI.Core/Layers/IFrameLayer.cs new file mode 100644 index 0000000..3dd29a4 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/Layers/IFrameLayer.cs @@ -0,0 +1,27 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + + +using Catalyst.Domains; + +namespace Catalyst.Layers { + + /// + /// Represents the frame layer in the CatalystUI model. + /// + /// + public interface IFrameLayer : ILayer where TDomain : IDomain { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Core/Layers/IRendererLayer.cs b/CatalystUI/Core/CatalystUI.Core/Layers/IRendererLayer.cs new file mode 100644 index 0000000..c4f4e7f --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/Layers/IRendererLayer.cs @@ -0,0 +1,26 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Domains; + +namespace Catalyst.Layers { + + /// + /// Represents the renderer layer in the CatalystUI model. + /// + /// + public interface IRendererLayer : ILayer where TDomain : IDomain { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Core/Layers/ISemanticsLayer.cs b/CatalystUI/Core/CatalystUI.Core/Layers/ISemanticsLayer.cs new file mode 100644 index 0000000..158c2ce --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/Layers/ISemanticsLayer.cs @@ -0,0 +1,26 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Domains; + +namespace Catalyst.Layers { + + /// + /// Represents the semantics layer in the CatalystUI model. + /// + /// + public interface ISemanticsLayer : ILayer where TDomain : IDomain { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Core/Layers/ISystemLayer.cs b/CatalystUI/Core/CatalystUI.Core/Layers/ISystemLayer.cs new file mode 100644 index 0000000..a9367a4 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/Layers/ISystemLayer.cs @@ -0,0 +1,26 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Domains; + +namespace Catalyst.Layers { + + /// + /// Represents the system layer in the CatalystUI model. + /// + /// + public interface ISystemLayer : ILayer where TDomain : IDomain { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Core/Layers/IWindowLayer.cs b/CatalystUI/Core/CatalystUI.Core/Layers/IWindowLayer.cs new file mode 100644 index 0000000..a34a35d --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/Layers/IWindowLayer.cs @@ -0,0 +1,26 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Domains; + +namespace Catalyst.Layers { + + /// + /// Represents the window layer in the CatalystUI model. + /// + /// + public interface IWindowLayer : ILayer where TDomain : IDomain { + + // ... + + } + +} \ No newline at end of file From 60b21ec172f4eb620592a6a592ddab6e5c8c09cd Mon Sep 17 00:00:00 2001 From: FireController#1847 Date: Sat, 25 Oct 2025 13:10:26 -0600 Subject: [PATCH 03/20] Add the core connector interfaces * Imported the primary connector interfaces from previous project iterations. --- .../Connectors/IAdapterConnector.cs | 27 +++++++++++++++++++ .../Connectors/IBridgeConnector.cs | 27 +++++++++++++++++++ .../Connectors/IDataConnector.cs | 27 +++++++++++++++++++ .../Connectors/INativeConnector.cs | 27 +++++++++++++++++++ .../Connectors/IParserConnector.cs | 27 +++++++++++++++++++ .../Connectors/ISurfaceConnector.cs | 27 +++++++++++++++++++ 6 files changed, 162 insertions(+) create mode 100644 CatalystUI/Core/CatalystUI.Core/Connectors/IAdapterConnector.cs create mode 100644 CatalystUI/Core/CatalystUI.Core/Connectors/IBridgeConnector.cs create mode 100644 CatalystUI/Core/CatalystUI.Core/Connectors/IDataConnector.cs create mode 100644 CatalystUI/Core/CatalystUI.Core/Connectors/INativeConnector.cs create mode 100644 CatalystUI/Core/CatalystUI.Core/Connectors/IParserConnector.cs create mode 100644 CatalystUI/Core/CatalystUI.Core/Connectors/ISurfaceConnector.cs diff --git a/CatalystUI/Core/CatalystUI.Core/Connectors/IAdapterConnector.cs b/CatalystUI/Core/CatalystUI.Core/Connectors/IAdapterConnector.cs new file mode 100644 index 0000000..445da68 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/Connectors/IAdapterConnector.cs @@ -0,0 +1,27 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Domains; +using Catalyst.Layers; + +namespace Catalyst.Connectors { + + /// + /// Represents the adapter connector in the CatalystUI model. + /// + /// + public interface IAdapterConnector : IConnector where TLayerLow : IFrameLayer where TLayerHigh : IComponentsLayer { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Core/Connectors/IBridgeConnector.cs b/CatalystUI/Core/CatalystUI.Core/Connectors/IBridgeConnector.cs new file mode 100644 index 0000000..2b675b0 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/Connectors/IBridgeConnector.cs @@ -0,0 +1,27 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Domains; +using Catalyst.Layers; + +namespace Catalyst.Connectors { + + /// + /// Represents the bridge connector in the CatalystUI model. + /// + /// + public interface IBridgeConnector : IConnector where TLayerLow : IRendererLayer where TLayerHigh : IFrameLayer { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Core/Connectors/IDataConnector.cs b/CatalystUI/Core/CatalystUI.Core/Connectors/IDataConnector.cs new file mode 100644 index 0000000..da20dbc --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/Connectors/IDataConnector.cs @@ -0,0 +1,27 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Domains; +using Catalyst.Layers; + +namespace Catalyst.Connectors { + + /// + /// Represents the data connector in the CatalystUI model. + /// + /// + public interface IDataConnector : IConnector where TLayerLow : ISemanticsLayer where TLayerHigh : IDataLayer { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Core/Connectors/INativeConnector.cs b/CatalystUI/Core/CatalystUI.Core/Connectors/INativeConnector.cs new file mode 100644 index 0000000..b16e6ed --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/Connectors/INativeConnector.cs @@ -0,0 +1,27 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Domains; +using Catalyst.Layers; + +namespace Catalyst.Connectors { + + /// + /// Represents the native connector in the CatalystUI model. + /// + /// + public interface INativeConnector : IConnector where TLayerLow : ISystemLayer where TLayerHigh : IWindowLayer { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Core/Connectors/IParserConnector.cs b/CatalystUI/Core/CatalystUI.Core/Connectors/IParserConnector.cs new file mode 100644 index 0000000..6d2e5fc --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/Connectors/IParserConnector.cs @@ -0,0 +1,27 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Domains; +using Catalyst.Layers; + +namespace Catalyst.Connectors { + + /// + /// Represents the parser connector in the CatalystUI model. + /// + /// + public interface IParserConnector : IConnector where TLayerLow : IComponentsLayer where TLayerHigh : ISemanticsLayer { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Core/Connectors/ISurfaceConnector.cs b/CatalystUI/Core/CatalystUI.Core/Connectors/ISurfaceConnector.cs new file mode 100644 index 0000000..3116d6d --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/Connectors/ISurfaceConnector.cs @@ -0,0 +1,27 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Domains; +using Catalyst.Layers; + +namespace Catalyst.Connectors { + + /// + /// Represents the surface connector in the CatalystUI model. + /// + /// + public interface ISurfaceConnector : IConnector where TLayerLow : IWindowLayer where TLayerHigh : IRendererLayer { + + // ... + + } + +} \ No newline at end of file From e3b3a50ce9cc1f13d0926567fb5cbe9066b81672 Mon Sep 17 00:00:00 2001 From: FireController#1847 Date: Sat, 25 Oct 2025 13:12:06 -0600 Subject: [PATCH 04/20] Add the core interaction interfaces * Imported the primary interaction interfaces from previous project iterations. --- .../Interactions/IInputDevice.cs | 23 ++++++++++++++++ .../Interactions/IInteraction.cs | 27 +++++++++++++++++++ .../Interactions/Input/IHardwareDevice.cs | 24 +++++++++++++++++ .../Interactions/Input/ILogicalDevice.cs | 24 +++++++++++++++++ 4 files changed, 98 insertions(+) create mode 100644 CatalystUI/Core/CatalystUI.Core/Interactions/IInputDevice.cs create mode 100644 CatalystUI/Core/CatalystUI.Core/Interactions/IInteraction.cs create mode 100644 CatalystUI/Core/CatalystUI.Core/Interactions/Input/IHardwareDevice.cs create mode 100644 CatalystUI/Core/CatalystUI.Core/Interactions/Input/ILogicalDevice.cs diff --git a/CatalystUI/Core/CatalystUI.Core/Interactions/IInputDevice.cs b/CatalystUI/Core/CatalystUI.Core/Interactions/IInputDevice.cs new file mode 100644 index 0000000..c8358c7 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/Interactions/IInputDevice.cs @@ -0,0 +1,23 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +namespace Catalyst.Interactions { + + /// + /// Represents the originating input device for an interaction. + /// + public interface IInputDevice { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Core/Interactions/IInteraction.cs b/CatalystUI/Core/CatalystUI.Core/Interactions/IInteraction.cs new file mode 100644 index 0000000..b5a1d22 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/Interactions/IInteraction.cs @@ -0,0 +1,27 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +namespace Catalyst.Interactions { + + /// + /// Represents an interaction in the CatalystUI model. + /// + public interface IInteraction { + + /// + /// Gets the input device that originated the interaction. + /// + /// The input device, or if the origin is unknown. + IInputDevice? InputDevice { get; } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Core/Interactions/Input/IHardwareDevice.cs b/CatalystUI/Core/CatalystUI.Core/Interactions/Input/IHardwareDevice.cs new file mode 100644 index 0000000..097c8a7 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/Interactions/Input/IHardwareDevice.cs @@ -0,0 +1,24 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +namespace Catalyst.Interactions.Input { + + /// + /// Represents a physical hardware input device, + /// such as a mouse, keyboard, or touch screen. + /// + public interface IHardwareDevice : IInputDevice { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Core/Interactions/Input/ILogicalDevice.cs b/CatalystUI/Core/CatalystUI.Core/Interactions/Input/ILogicalDevice.cs new file mode 100644 index 0000000..ee1cd44 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/Interactions/Input/ILogicalDevice.cs @@ -0,0 +1,24 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +namespace Catalyst.Interactions.Input { + + /// + /// Represents a logical input device, indicating the interaction + /// did not originate from a physical hardware device. + /// + public interface ILogicalDevice : IInputDevice { + + // ... + + } + +} \ No newline at end of file From 5f7a05212cedae02443e4687f91d05dddcbed978 Mon Sep 17 00:00:00 2001 From: FireController#1847 Date: Sat, 25 Oct 2025 13:47:36 -0600 Subject: [PATCH 05/20] Add the core debugging classes * Imported the core debugging classes from previous project iterations. * Major modifications were made to CatalystDebug.cs to extract and unify certain sections of code, including but not limited to: * CatalystDebugConfiguration * CatalystDebugOptions --- .../Debugging/CatalystDebug.cs | 382 ++++++++++++++++++ .../Debugging/CatalystDebugConfiguration.cs | 49 +++ .../Debugging/CatalystDebugOptions.cs | 93 +++++ .../CatalystUI.Core/Debugging/DebugContext.cs | 143 +++++++ .../CatalystUI.Core/Debugging/LogLevel.cs | 57 +++ .../Debugging/NoopDebugContext.cs | 33 ++ 6 files changed, 757 insertions(+) create mode 100644 CatalystUI/Core/CatalystUI.Core/Debugging/CatalystDebug.cs create mode 100644 CatalystUI/Core/CatalystUI.Core/Debugging/CatalystDebugConfiguration.cs create mode 100644 CatalystUI/Core/CatalystUI.Core/Debugging/CatalystDebugOptions.cs create mode 100644 CatalystUI/Core/CatalystUI.Core/Debugging/DebugContext.cs create mode 100644 CatalystUI/Core/CatalystUI.Core/Debugging/LogLevel.cs create mode 100644 CatalystUI/Core/CatalystUI.Core/Debugging/NoopDebugContext.cs diff --git a/CatalystUI/Core/CatalystUI.Core/Debugging/CatalystDebug.cs b/CatalystUI/Core/CatalystUI.Core/Debugging/CatalystDebug.cs new file mode 100644 index 0000000..e4d4f8b --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/Debugging/CatalystDebug.cs @@ -0,0 +1,382 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; + +namespace Catalyst.Debugging { + + /// + /// Container class for CatalystUI debugging utilities. + /// + public static class CatalystDebug { + + /// + /// The environment variable prefix for CatalystUI debugging settings. + /// + public const string ENV_PREFIX = "CATALYST_DEBUG_"; + + /// + /// The name of the CatalystUI debugging configuration file. + /// + public const string CONFIG_FILE_NAME = "catdebug.ini"; + + /// + /// The name of the CatalystUI debugging output log file. + /// + public const string OUTPUT_FILE_NAME = "catdebug.log"; + + // The following is a list of primary sections and keys which + // are used in the configuration file for Catalyst debugging. +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + public const string DEBUG_SECTION_LOGGER = "Logger"; + public const string DEBUG_KVP_LOGGER_MINIMUM_LEVEL = "MinimumLevel"; + public const string DEBUG_KVP_LOGGER_SHOW_THREAD = "ShowThread"; + public const string DEBUG_KVP_LOGGER_SHOW_FILENAME = "ShowFileName"; + public const string DEBUG_KVP_LOGGER_SHOW_METHOD_NAME = "ShowMethodName"; + public const string DEBUG_KVP_LOGGER_SHOW_LINE_NUMBER = "ShowLineNumber"; + public const string DEBUG_KVP_LOGGER_SHOW_STACK_TRACE = "ShowStackTrace"; + public const string DEBUG_SECTION_ENABLED_SCOPES = "EnabledScopes"; +#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member + + /// + /// Gets or sets the current CatalystUI debugging configuration. + /// + /// The current instance. + public static CatalystDebugConfiguration? DebugConfiguration { get; private set; } + + /// + /// Gets or sets the current CatalystUI debugging options. + /// + /// The current instance. + public static CatalystDebugOptions DebugOptions { get; private set; } + + /// + /// A cache of configuration properties which + /// have been attempted to be loaded. + /// + private static readonly ConcurrentDictionary _configCache; + + /// + /// A dictionary of enabled scopes for debugging. + /// + private static readonly ConcurrentDictionary _enabledScopes; + + /// + /// A constructor for creating instances. + /// + private static Func? _debugContextConstructor; + + /// + /// Static initializer for . + /// + static CatalystDebug() { + // Fields + _configCache = new(); + _enabledScopes = new(); + _debugContextConstructor = null; + + // Properties + DebugConfiguration = LoadDebugConfiguration(); + DebugOptions = LoadDebugOptions(); + + // Preload scopes + PreloadScopes(); + } + + /// + /// Loads the CatalystUI debugging configuration from file and environment variables. + /// + /// The loaded instance. + private static CatalystDebugConfiguration? LoadDebugConfiguration() { + try { + if (!File.Exists(CONFIG_FILE_NAME)) return null; + using FileStream stream = File.OpenRead(CONFIG_FILE_NAME); + + // The following code is adapted from previous iterations + // of the CatalystUI Arcane project. To prevent interdependency, + // only the necessary code has been copied here. + + // COPIED CODE START + + // Read and parse the file into basic structures + Dictionary fileEntries = [ ]; + Dictionary> fileSections = [ ]; + using StreamReader reader = new(stream, leaveOpen: true); + string? section = null; + while (reader.ReadLine() is { } line) { + line = line.Trim(); + if (string.IsNullOrWhiteSpace(line) || line.StartsWith(';')) continue; // Skip empty lines and comments + + // Parse section tag + if (line.StartsWith('[') && line.EndsWith(']')) { + // New section + section = line[1..^1].Trim(); + if (string.IsNullOrWhiteSpace(section)) section = null; // don't accept empty sections + continue; + } + + // Split key-value pairs + int index = line.IndexOf('='); + if (index < 0) continue; // No key-value pair found + string key = line[..index].Trim(); + string? value = line[(index + 1)..].Trim(); + if (string.IsNullOrWhiteSpace(value)) value = null; // Treat empty values as null + if (value != null && (value = value.Trim()).Length >= 4) { // Check for "null" value + int idx = value.IndexOf("null", StringComparison.OrdinalIgnoreCase); + if (idx >= 0 && + (idx == 0 || value[..idx].All(c => !char.IsLetterOrDigit(c))) && + (idx + 4 == value.Length || value[(idx + 4)..].All(c => !char.IsLetterOrDigit(c)))) + value = null; + } + + // If we have a section, add to it; otherwise, add to global entries + if (section != null) { + if (!fileSections.TryGetValue(section, out Dictionary? sectionEntries)) { + sectionEntries = new(); + fileSections[section] = sectionEntries; + } + sectionEntries[key] = value; + } else { + fileEntries[key] = value; + } + } + + // COPIED CODE END + + // Cast sections to read-only dictionaries + Dictionary> readonlySections = + fileSections.ToDictionary( + kvp => kvp.Key, + IReadOnlyDictionary (kvp) => kvp.Value + ); + + // Return the constructed configuration + return new CatalystDebugConfiguration( + globalEntries: fileEntries, + sections: readonlySections + ); + } catch (Exception e) { + Trace.TraceWarning($"⚠️ [Catalyst.Debugging] Failed to load the debug configuration file. {e.Message}{Environment.NewLine}{e.StackTrace}"); + return null; + } + } + + /// + /// Gets a configuration value from the CatalystUI debugging configuration. + /// + /// The section in the configuration file to look for, or to use the global section. + /// The key to retrieve the value for. + /// The value retrieved from the configuration file or environment variable. + /// If , skips logging the retrieval of the configuration value. + /// if the value was found; otherwise, . + public static bool TryGetConfigValue(string? section, string key, [NotNullWhen(true)] out string? value, bool skipLog = false) { + // First, check the cache + if (_configCache.TryGetValue(key, out value)) { + return value != null; // the value is always found if it was cached + } + + // First, check the environment variable + string env; + if (section != null) { + env = ENV_PREFIX + section.ToUpperInvariant() + "_" + key.ToUpperInvariant(); + } else { + env = ENV_PREFIX + key.ToUpperInvariant(); + } + value = Environment.GetEnvironmentVariable(env); + if (value != null) { + if (!skipLog) { + Trace.WriteLine($"ℹ️ [Catalyst.Debugging] Configuration value '{key}' found in environment variable '{env}' with value: {value}"); + } + _configCache.TryAdd(key, value); + return true; + } + + // Second, check the configuration file + if (DebugConfiguration != null) { + CatalystDebugConfiguration configFile = DebugConfiguration.Value; + if (!configFile.GlobalEntries.TryGetValue(key, out value) && section != null) { + if (configFile.Sections.TryGetValue(section, out IReadOnlyDictionary? sectionEntries)) { + _ = sectionEntries.TryGetValue(key, out value); + } + } + if (value != null) { + _configCache.TryAdd(key, value); + if (!skipLog) { + Trace.WriteLine($"ℹ️ [Catalyst.Debugging] Configuration value '{key}' found in section '{section ?? "Global"}' with value: {value}"); + } + return true; + } + } + + // Otherwise, we couldn't find it + value = null; + _configCache.TryAdd(key, null); + return false; + } + + /// + /// Determines the log event level from a string representation. + /// + /// The string representation of the log level. + /// The corresponding log event level or if not recognized. + public static LogLevel? LogLevelFromString(string level) { + return level.Trim().ToLowerInvariant() switch { + "critical" => LogLevel.Critical, + "fatal" => LogLevel.Critical, // Alias for Critical + "error" => LogLevel.Error, + "exception" => LogLevel.Error, // Alias for Error + "warning" => LogLevel.Warning, + "information" => LogLevel.Info, + "info" => LogLevel.Info, // Alias for Information + "debug" => LogLevel.Debug, + "verbose" => LogLevel.Verbose, + _ => null + }; + } + + /// + /// Loads the CatalystUI debugging options from configuration. + /// + /// The loaded instance. + private static CatalystDebugOptions LoadDebugOptions() { + LogLevel level = LogLevel.Debug; + bool showThread = true; + bool showFileName = false; + bool showMethodName = false; + bool showLineNumber = false; + bool showStackTrace = false; + + // Fetch each associated configuration value + if (TryGetConfigValue(DEBUG_SECTION_LOGGER, DEBUG_KVP_LOGGER_MINIMUM_LEVEL, out string? configLogLevel)) { + if (LogLevelFromString(configLogLevel) is LogLevel parsedLevel) { + level = parsedLevel; + } + } + if (TryGetConfigValue(DEBUG_SECTION_LOGGER, DEBUG_KVP_LOGGER_SHOW_THREAD, out string? configShowThread)) { + if (bool.TryParse(configShowThread, out bool parsedShowThread)) { + showThread = parsedShowThread; + } + } + if (TryGetConfigValue(DEBUG_SECTION_LOGGER, DEBUG_KVP_LOGGER_SHOW_FILENAME, out string? configShowFileName)) { + if (bool.TryParse(configShowFileName, out bool parsedShowFileName)) { + showFileName = parsedShowFileName; + } + } + if (TryGetConfigValue(DEBUG_SECTION_LOGGER, DEBUG_KVP_LOGGER_SHOW_METHOD_NAME, out string? configShowMethodName)) { + if (bool.TryParse(configShowMethodName, out bool parsedShowMethodName)) { + showMethodName = parsedShowMethodName; + } + } + if (TryGetConfigValue(DEBUG_SECTION_LOGGER, DEBUG_KVP_LOGGER_SHOW_LINE_NUMBER, out string? configShowLineNumber)) { + if (bool.TryParse(configShowLineNumber, out bool parsedShowLineNumber)) { + showLineNumber = parsedShowLineNumber; + } + } + if (TryGetConfigValue(DEBUG_SECTION_LOGGER, DEBUG_KVP_LOGGER_SHOW_STACK_TRACE, out string? configShowStackTrace)) { + if (bool.TryParse(configShowStackTrace, out bool parsedShowStackTrace)) { + showStackTrace = parsedShowStackTrace; + } + } + + // Return the constructed options + return new( + minimumLogLevel: level, + showsThread: showThread, + showsFileName: showFileName, + showsMethodName: showMethodName, + showsLineNumber: showLineNumber, + showsStackTrace: showStackTrace + ); + } + + /// + /// Determines the log level for a given scope. + /// + /// The scope to determine the log level for. + /// The determined for the scope. + public static LogLevel DetermineScopeLevel(string scope) { + if (!_enabledScopes.TryGetValue(scope, out LogLevel level)) { + level = DebugOptions.MinimumLogLevel; + if (TryGetConfigValue(DEBUG_SECTION_ENABLED_SCOPES, scope, out string? configValue, true)) { + LogLevel? parsedResult = LogLevelFromString(configValue); + if (parsedResult == null) throw new InvalidOperationException($"Invalid log level '{configValue}' for scope '{scope}'. Using default level: {DebugOptions.MinimumLogLevel}"); + level = parsedResult.Value; + _enabledScopes.TryAdd(scope, level); + } else { + Trace.WriteLine($"⚠️ Scope '{scope}' is not defined in the configuration file. Using default level: {DebugOptions.MinimumLogLevel}"); + } + + // Scope output + if (DebugOptions.MinimumLogLevel >= LogLevel.Debug) { + Trace.WriteLine($"ℹ️ Determined scope '{scope}' level: {level}"); + } + } + return level; + } + + /// + /// Preloads all existing scopes from the configuration. + /// + private static void PreloadScopes() { + // Scan through scopes and check if they are enabled + if (DebugConfiguration != null && DebugConfiguration.Value.Sections.TryGetValue(DEBUG_SECTION_ENABLED_SCOPES, out IReadOnlyDictionary? sectionEntries)) { + foreach (KeyValuePair entry in sectionEntries) { + // Determine the scope level + try { + DetermineScopeLevel(entry.Key); + } catch { + // ignore failed scope level determination + // special configuration values may use this section to store additional + // information for each scope, so we don't want to throw an error here + // + // an error is thrown in ForContext() if the scope level is invalid, + // however, since the property is expected to be used as a log level + // for the associated scope + } + } + } + } + + /// + /// Injects a custom constructor for creating instances. + /// + /// + /// Custom implementations of can be used to provide custom logging or debugging behavior. + /// The constructor should return a new instance of or + /// an extended variety that overrides the default behavior. + /// + /// A function that constructs a new . + public static void InjectDebugContext(Func constructor) { + if (_debugContextConstructor != null) { + Trace.WriteLine("⚠️ A debug constructor has already been set. The previous constructor will be replaced with the new one."); + Trace.WriteLine(constructor.GetType().GetGenericArguments()[1].FullName); + } + _debugContextConstructor = constructor; + } + + /// + /// Constructs a new debug context for the specified scope. + /// + /// The scope of the debug context. + /// A new instance of for the specified scope. + public static DebugContext ForContext(string scope) { + return _debugContextConstructor == null ? new NoopDebugContext(scope) : _debugContextConstructor(scope); + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Core/Debugging/CatalystDebugConfiguration.cs b/CatalystUI/Core/CatalystUI.Core/Debugging/CatalystDebugConfiguration.cs new file mode 100644 index 0000000..63a691c --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/Debugging/CatalystDebugConfiguration.cs @@ -0,0 +1,49 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace Catalyst.Debugging { + + /// + /// The debug configuration file for CatalystUI. + /// + public readonly record struct CatalystDebugConfiguration { + + /// + /// Gets a raw list of parsed global entries from the configuration file. + /// + /// The global entries dictionary. + public required IReadOnlyDictionary GlobalEntries { get; init; } + + /// + /// Gets a raw list of parsed sections from the configuration file. + /// + /// The sections dictionary. + public required IReadOnlyDictionary> Sections { get; init; } + + /// + /// Constructs a new . + /// + /// The global entries. + /// The sections. + [SetsRequiredMembers] + public CatalystDebugConfiguration( + IReadOnlyDictionary globalEntries, + IReadOnlyDictionary> sections) { + GlobalEntries = globalEntries; + Sections = sections; + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Core/Debugging/CatalystDebugOptions.cs b/CatalystUI/Core/CatalystUI.Core/Debugging/CatalystDebugOptions.cs new file mode 100644 index 0000000..b1f90ed --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/Debugging/CatalystDebugOptions.cs @@ -0,0 +1,93 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System.Diagnostics.CodeAnalysis; + +namespace Catalyst.Debugging { + + /// + /// Options which can be set to modify the way Catalyst debugging works. + /// + public readonly record struct CatalystDebugOptions { + + /// + /// Gets the minimum logging level for Catalyst debugging. + /// + /// The minimum logging level. + public required LogLevel MinimumLogLevel { get; init; } + + /// + /// Gets a flag indicating whether thread information should be shown in log entries. + /// + /// if thread information should be shown; otherwise, . + public required bool ShowsThread { get; init; } + + /// + /// Gets a flag indicating whether file names should be shown in log entries. + /// + /// if file names should be shown; otherwise, . + public required bool ShowsFileName { get; init; } + + /// + /// Gets a flag indicating whether method names should be shown in log entries. + /// + /// if method names should be shown; otherwise, . + public required bool ShowsMethodName { get; init; } + + /// + /// Gets a flag indicating whether line numbers should be shown in log entries. + /// + /// if line numbers should be shown; otherwise, . + public required bool ShowsLineNumber { get; init; } + + /// + /// Gets a flag indicating whether stack traces should be shown in log entries. + /// + /// if stack traces should be shown; otherwise, . + public required bool ShowsStackTrace { get; init; } + + /// + /// Constructs a new with + /// default settings. + /// + [SetsRequiredMembers] + public CatalystDebugOptions() { + MinimumLogLevel = LogLevel.Debug; + ShowsThread = true; + ShowsFileName = false; + ShowsMethodName = false; + ShowsLineNumber = false; + ShowsStackTrace = false; + } + + /// + /// Constructs a new with + /// the specified settings. + /// + [SetsRequiredMembers] + public CatalystDebugOptions( + LogLevel minimumLogLevel, + bool showsThread, + bool showsFileName, + bool showsMethodName, + bool showsLineNumber, + bool showsStackTrace) { + MinimumLogLevel = minimumLogLevel; + ShowsThread = showsThread; + ShowsFileName = showsFileName; + ShowsMethodName = showsMethodName; + ShowsLineNumber = showsLineNumber; + ShowsStackTrace = showsStackTrace; + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Core/Debugging/DebugContext.cs b/CatalystUI/Core/CatalystUI.Core/Debugging/DebugContext.cs new file mode 100644 index 0000000..e0d6606 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/Debugging/DebugContext.cs @@ -0,0 +1,143 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System.Diagnostics; + +namespace Catalyst.Debugging { + + /// + /// A context for debugging operations within the CatalystUI framework. + /// + public abstract class DebugContext { + + /// + /// Gets the scope of the debug context. + /// + /// The scope as a string. + public string Scope { get; } + + /// + /// Gets or sets the prefix for debug messages. + /// + /// + /// No additional formatting is applied to the prefix. + /// + /// The prefix as a string. + public string? Prefix { get; protected set; } + + /// + /// Gets or sets the log level for the debug context. + /// + /// The log level. + public LogLevel Level { get; protected set; } + + /// + /// Constructs a new with + /// the specified scope. + /// + /// + protected DebugContext(string scope) { + Scope = scope; + Prefix = null; + Level = CatalystDebug.DebugOptions.MinimumLogLevel; + } + + /// + /// Logs to the logger with the specified log level, message, and optional prefix and arguments. + /// + /// The log level for the message. + /// The message to log. + /// An optional prefix to prepend to the message. + /// Optional arguments to stringify at the end of the message. + [DebuggerHidden] + public virtual void Log(LogLevel level, string message, string? prefix = null, params object[] args) { + // no-op since internal library calls will not be filtered out by the compiler + // this is an unfortunate compromise to allow for debug logging from the library itself + // in addition to providing the debug functionality for the user + // any calls to the utility methods will be stripped by the compiler in release builds + // of both the library and the user's application, which is intended functionality + } + + /// + /// Logs a fatal error with the specified message and optional arguments. + /// + /// + [Conditional("DEBUG")] + public void LogFatal(string message, params object[] args) { + if (Level < LogLevel.Critical) return; + Log(LogLevel.Critical, message, null, args); + } + + /// + [Conditional("DEBUG")] + public void LogCritical(string message, params object[] args) => LogFatal(message, args); + + /// + /// Logs an error with the specified message and optional arguments. + /// + /// + /// If an exception is provided as an argument, the exception's will be included in the log output. + /// If the message is null or empty, it will be replaced with the exception's + /// + /// + [Conditional("DEBUG")] + public void LogError(string message, params object[] args) { + if (Level < LogLevel.Error) return; + Log(LogLevel.Error, message, null, args); + } + + /// + [Conditional("DEBUG")] + public void LogException(string message, params object[] args) => LogError(message, args); + + /// + /// Logs a warning with the specified message and optional arguments. + /// + /// + [Conditional("DEBUG")] + public void LogWarning(string message, params object[] args) { + if (Level < LogLevel.Warning) return; + Log(LogLevel.Warning, message, null, args); + } + + /// + /// Logs an informational message with the specified message and optional arguments. + /// + /// + [Conditional("DEBUG")] + public void LogInfo(string message, params object[] args) { + if (Level < LogLevel.Info) return; + Log(LogLevel.Info, message, null, args); + } + + /// + /// Logs a debug message with the specified message and optional arguments. + /// + /// + [Conditional("DEBUG")] + public void LogDebug(string message, params object[] args) { + if (Level < LogLevel.Debug) return; + Log(LogLevel.Debug, message, null, args); + } + + /// + /// Logs a verbose message with the specified message and optional arguments. + /// + /// + [Conditional("DEBUG")] + public void LogVerbose(string message, params object[] args) { + if (Level < LogLevel.Verbose) return; + Log(LogLevel.Verbose, message, null, args); + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Core/Debugging/LogLevel.cs b/CatalystUI/Core/CatalystUI.Core/Debugging/LogLevel.cs new file mode 100644 index 0000000..b4bbb8d --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/Debugging/LogLevel.cs @@ -0,0 +1,57 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + + +namespace Catalyst.Debugging { + + /// + /// A list of recognized log levels for debugging and logging within the CatalystUI framework. + /// + public enum LogLevel { + + /// + /// No logs are emitted or processed. + /// + None = 0, + + /// + /// Logs only critical errors that require immediate attention, such as application crashes or severe failures. + /// + Critical = 1, + + /// + /// Logs critical errors and errors that occur but do not require immediate attention. + /// + Error = 2, + + /// + /// Logs errors and additional warnings that may indicate potential issues. + /// + Warning = 3, + + /// + /// Logs errors, warnings, and informational messages that provide insights into the application's state. + /// + Info = 4, + + /// + /// Logs errors, warnings, informational messages, and detailed debugging information useful for diagnosing issues. + /// + Debug = 5, + + /// + /// Logs all messages, including detailed debug information, performance metrics, and verbose output for in-depth analysis. + /// + Verbose = 6 + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Core/Debugging/NoopDebugContext.cs b/CatalystUI/Core/CatalystUI.Core/Debugging/NoopDebugContext.cs new file mode 100644 index 0000000..1a1aa7d --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/Debugging/NoopDebugContext.cs @@ -0,0 +1,33 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +namespace Catalyst.Debugging { + + /// + /// Should be avoided whenever possible, a fallback debug context which is used + /// by the static class when no custom debug context + /// is provided. + /// + public sealed class NoopDebugContext : DebugContext { + + /// + internal NoopDebugContext(string scope) : base(scope) { + // ... + } + + /// + public override void Log(LogLevel level, string message, string? prefix = null, params object[] args) { + // no-op + } + + } + +} \ No newline at end of file From a6191f3e84f7c451a04f1e0c2f834516fd04324d Mon Sep 17 00:00:00 2001 From: FireController#1847 Date: Sat, 25 Oct 2025 13:50:08 -0600 Subject: [PATCH 06/20] Add Tooling/CatalystUI.Profiling * Imported the profiling project from previous project iterations. * No additional changes were made. --- CatalystUI/CatalystUI.sln | 9 +++ .../CatalystUI.Profiling.csproj | 48 ++++++++++++++++ .../CatalystUI.Profiling/Profile.template.cs | 56 +++++++++++++++++++ .../Tooling/CatalystUI.Profiling/Program.cs | 38 +++++++++++++ .../PublishProfiles/PublishProfiling.pubxml | 37 ++++++++++++ .../Properties/catdebug.template.ini | 12 ++++ .../Properties/catdeps.template.props | 7 +++ 7 files changed, 207 insertions(+) create mode 100644 CatalystUI/Tooling/CatalystUI.Profiling/CatalystUI.Profiling.csproj create mode 100644 CatalystUI/Tooling/CatalystUI.Profiling/Profile.template.cs create mode 100644 CatalystUI/Tooling/CatalystUI.Profiling/Program.cs create mode 100644 CatalystUI/Tooling/CatalystUI.Profiling/Properties/PublishProfiles/PublishProfiling.pubxml create mode 100644 CatalystUI/Tooling/CatalystUI.Profiling/Properties/catdebug.template.ini create mode 100644 CatalystUI/Tooling/CatalystUI.Profiling/Properties/catdeps.template.props diff --git a/CatalystUI/CatalystUI.sln b/CatalystUI/CatalystUI.sln index c3a3324..ec65762 100644 --- a/CatalystUI/CatalystUI.sln +++ b/CatalystUI/CatalystUI.sln @@ -4,6 +4,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{7EC51871-4 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CatalystUI.Core", "Core\CatalystUI.Core\CatalystUI.Core.csproj", "{68F496AC-9438-40F1-9DF8-97363033D661}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tooling", "Tooling", "{5D38F696-8C11-4C9A-B50E-2C33AA7FAA6C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CatalystUI.Profiling", "Tooling\CatalystUI.Profiling\CatalystUI.Profiling.csproj", "{10856DCF-AD1F-45C2-B995-E36CA4F8751B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -14,8 +18,13 @@ Global {68F496AC-9438-40F1-9DF8-97363033D661}.Debug|Any CPU.Build.0 = Debug|Any CPU {68F496AC-9438-40F1-9DF8-97363033D661}.Release|Any CPU.ActiveCfg = Release|Any CPU {68F496AC-9438-40F1-9DF8-97363033D661}.Release|Any CPU.Build.0 = Release|Any CPU + {10856DCF-AD1F-45C2-B995-E36CA4F8751B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {10856DCF-AD1F-45C2-B995-E36CA4F8751B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {10856DCF-AD1F-45C2-B995-E36CA4F8751B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {10856DCF-AD1F-45C2-B995-E36CA4F8751B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {68F496AC-9438-40F1-9DF8-97363033D661} = {7EC51871-49A8-4991-BDAF-F43A4E9B8C9D} + {10856DCF-AD1F-45C2-B995-E36CA4F8751B} = {5D38F696-8C11-4C9A-B50E-2C33AA7FAA6C} EndGlobalSection EndGlobal diff --git a/CatalystUI/Tooling/CatalystUI.Profiling/CatalystUI.Profiling.csproj b/CatalystUI/Tooling/CatalystUI.Profiling/CatalystUI.Profiling.csproj new file mode 100644 index 0000000..36ca046 --- /dev/null +++ b/CatalystUI/Tooling/CatalystUI.Profiling/CatalystUI.Profiling.csproj @@ -0,0 +1,48 @@ + + + + + + Exe + Catalyst.Profiling + Catalyst.Profiling + + + + + + + + + + + + + + $(DefineConstants);PROFILE_EXISTS + + + + + + + + + + + + + + + + + + + + + PreserveNewest + catdebug.ini + + + + \ No newline at end of file diff --git a/CatalystUI/Tooling/CatalystUI.Profiling/Profile.template.cs b/CatalystUI/Tooling/CatalystUI.Profiling/Profile.template.cs new file mode 100644 index 0000000..337546f --- /dev/null +++ b/CatalystUI/Tooling/CatalystUI.Profiling/Profile.template.cs @@ -0,0 +1,56 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Builders; +using Catalyst.Builders.Extensions; +using Catalyst.Debugging; + +namespace Catalyst.Profiling { + + /// + /// A template for profiling CatalystUI or a CatalystUI-based application. + /// + public static class ProfileTemplate { + + /// + /// The debug context for profiling operations. + /// + private static DebugContext _debug; + + /// + /// Static constructor to initialize the debug context. + /// + static Profile() { + _debug = null!; + } + + /// + /// Acts as the main entry point for the profiling code. + /// + public static void Entry(string[] args) { + new CatalystAppBuilder() +#if DEBUG + .UseCatalystDebug() +#endif + .Build(Run); + } + + /// + /// Runs the profiling code. + /// + public static void Run(CatalystApp app) { + _debug = CatalystDebug.ForContext("Profiling"); + _debug.LogInfo("Hello, world!"); + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Tooling/CatalystUI.Profiling/Program.cs b/CatalystUI/Tooling/CatalystUI.Profiling/Program.cs new file mode 100644 index 0000000..a3bc2dc --- /dev/null +++ b/CatalystUI/Tooling/CatalystUI.Profiling/Program.cs @@ -0,0 +1,38 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +namespace Catalyst.Profiling { + + /// + /// Profiling program for CatalystUI or CatalystUI-based applications. + /// + /// + /// For usage information on this tool, please refer to the README.md file + /// in the project root or the official documentation. + /// + public static class Program { + + /// + /// Main entry point for the profiling tool. + /// + /// + /// Do not modify this method. Please refer to the official documentation for usage instructions. + /// + /// The command-line arguments. + public static void Main(string[] args) { +#if PROFILE_EXISTS + Profile.Entry(args); +#endif + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Tooling/CatalystUI.Profiling/Properties/PublishProfiles/PublishProfiling.pubxml b/CatalystUI/Tooling/CatalystUI.Profiling/Properties/PublishProfiles/PublishProfiling.pubxml new file mode 100644 index 0000000..35659c6 --- /dev/null +++ b/CatalystUI/Tooling/CatalystUI.Profiling/Properties/PublishProfiles/PublishProfiling.pubxml @@ -0,0 +1,37 @@ + + + + + + + Exe + true + true + true + true + true + true + + + ConsoleApp + bin\Release\net9.0\_publish\ + + + win-x64 + + + osx-x64 + + + linux-x64 + + + + + + + + + + + \ No newline at end of file diff --git a/CatalystUI/Tooling/CatalystUI.Profiling/Properties/catdebug.template.ini b/CatalystUI/Tooling/CatalystUI.Profiling/Properties/catdebug.template.ini new file mode 100644 index 0000000..075aa9c --- /dev/null +++ b/CatalystUI/Tooling/CatalystUI.Profiling/Properties/catdebug.template.ini @@ -0,0 +1,12 @@ +[Logger] +MinimumLevel=Debug +ShowThread=true +ShowFileName=false +ShowMethodName=false +ShowLineNumber=false +ShowStackTrace=false + +[EnabledScopes] +Debugging=info +Application=info +Profiling=info \ No newline at end of file diff --git a/CatalystUI/Tooling/CatalystUI.Profiling/Properties/catdeps.template.props b/CatalystUI/Tooling/CatalystUI.Profiling/Properties/catdeps.template.props new file mode 100644 index 0000000..1d3d73e --- /dev/null +++ b/CatalystUI/Tooling/CatalystUI.Profiling/Properties/catdeps.template.props @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file From a074dbcaaf1e6873cd749b1c09279544e8bb7a13 Mon Sep 17 00:00:00 2001 From: FireController#1847 Date: Sat, 25 Oct 2025 13:54:08 -0600 Subject: [PATCH 07/20] Add Core/CatalystUI.Collections * Imported the collections project from previous project iterations. * No additional changes were made other than bumping the version to beta.2. --- .scripts/Setup.ps1 | 3 +- CatalystUI/CatalystUI.sln | 7 + .../CatalystUI.Collections.csproj | 18 ++ .../CatalystUI.Collections/StaticArrayPool.cs | 212 ++++++++++++++++++ .../StaticArrayQueue.cs | 211 +++++++++++++++++ .../StaticArrayStack.cs | 182 +++++++++++++++ 6 files changed, 632 insertions(+), 1 deletion(-) create mode 100644 CatalystUI/Core/CatalystUI.Collections/CatalystUI.Collections.csproj create mode 100644 CatalystUI/Core/CatalystUI.Collections/StaticArrayPool.cs create mode 100644 CatalystUI/Core/CatalystUI.Collections/StaticArrayQueue.cs create mode 100644 CatalystUI/Core/CatalystUI.Collections/StaticArrayStack.cs diff --git a/.scripts/Setup.ps1 b/.scripts/Setup.ps1 index 4171528..50cef9e 100644 --- a/.scripts/Setup.ps1 +++ b/.scripts/Setup.ps1 @@ -43,9 +43,10 @@ $projectsList = @( @{ Module = "Core" Projects = @( + @{ Folder = "Core"; Name = "CatalystUI.Collections" }, @{ Folder = "Core"; Name = "CatalystUI.Core" } ) - PromptIgnore = $false + PromptIgnore = $true } ) diff --git a/CatalystUI/CatalystUI.sln b/CatalystUI/CatalystUI.sln index ec65762..c5576fe 100644 --- a/CatalystUI/CatalystUI.sln +++ b/CatalystUI/CatalystUI.sln @@ -8,6 +8,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tooling", "Tooling", "{5D38 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CatalystUI.Profiling", "Tooling\CatalystUI.Profiling\CatalystUI.Profiling.csproj", "{10856DCF-AD1F-45C2-B995-E36CA4F8751B}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CatalystUI.Collections", "Core\CatalystUI.Collections\CatalystUI.Collections.csproj", "{9B36BF4B-52A9-4881-8D01-391627D51AB9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -22,9 +24,14 @@ Global {10856DCF-AD1F-45C2-B995-E36CA4F8751B}.Debug|Any CPU.Build.0 = Debug|Any CPU {10856DCF-AD1F-45C2-B995-E36CA4F8751B}.Release|Any CPU.ActiveCfg = Release|Any CPU {10856DCF-AD1F-45C2-B995-E36CA4F8751B}.Release|Any CPU.Build.0 = Release|Any CPU + {9B36BF4B-52A9-4881-8D01-391627D51AB9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9B36BF4B-52A9-4881-8D01-391627D51AB9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9B36BF4B-52A9-4881-8D01-391627D51AB9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9B36BF4B-52A9-4881-8D01-391627D51AB9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {68F496AC-9438-40F1-9DF8-97363033D661} = {7EC51871-49A8-4991-BDAF-F43A4E9B8C9D} {10856DCF-AD1F-45C2-B995-E36CA4F8751B} = {5D38F696-8C11-4C9A-B50E-2C33AA7FAA6C} + {9B36BF4B-52A9-4881-8D01-391627D51AB9} = {7EC51871-49A8-4991-BDAF-F43A4E9B8C9D} EndGlobalSection EndGlobal diff --git a/CatalystUI/Core/CatalystUI.Collections/CatalystUI.Collections.csproj b/CatalystUI/Core/CatalystUI.Collections/CatalystUI.Collections.csproj new file mode 100644 index 0000000..e440558 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Collections/CatalystUI.Collections.csproj @@ -0,0 +1,18 @@ + + + + + + Catalyst.Collections + Catalyst.Collections + + + CatalystUI Collections + 1.0.0 + beta.2 + FireController#1847 + Collections API provided by the CatalystUI library. + CatalystUI,collections + + + \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Collections/StaticArrayPool.cs b/CatalystUI/Core/CatalystUI.Collections/StaticArrayPool.cs new file mode 100644 index 0000000..ebeb3d2 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Collections/StaticArrayPool.cs @@ -0,0 +1,212 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; + +namespace Catalyst.Collections { + + /// + /// A fixed-size pool implementation with zero memory + /// allocations after initialization. + /// + /// + /// + /// Allocations should be made using the method, + /// which returns a reference to the next available slot in the pool. Once + /// updated, the method should be fired, which + /// returns the index of the newly allocated element and increments + /// the count by one. + /// + /// + /// To release an allocated element, the + /// method should be called with the index of the element to release. + /// + /// + /// The type of elements stored in the pool. + public sealed class StaticArrayPool : IReadOnlyCollection where T : struct { + + /// + /// The array that holds the elements of the pool. + /// + private readonly T[] _arr; + + /// + /// A boolean array that indicates which elements in the pool are free. + /// + private readonly bool[] _free; + + /// + /// A stack which holds the indices of allocated elements. + /// + private readonly StaticArrayStack _allocated; + + /// + /// The current number of elements in the pool. + /// + private int _count; + + /// + /// Gets the current number of elements in the pool. + /// + /// The pool's current element count. + public int Count => _count; + + /// + /// Gets the total allocated capacity of the pool. + /// + /// The pool's total allocated capacity. + public int Capacity => _arr.Length; + + /// + /// Gets the underlying array of the pool. + /// + /// + /// + /// INTERNAL USE ONLY. + ///
+ /// Version consistency is not guaranteed, + /// and the property may be changed without notice. + ///
+ /// + /// Warning: Fetching the underlying array + /// provides access to the raw data of the pool, meaning + /// the return value will provide both valid and invalid + /// instances of the underlying struct type. Prefer + /// enumeration via whenever + /// possible. This property is provided for advanced use cases + /// and/or disposal of the underlying structures, and should + /// be used with special care, attention, and caution. + /// + ///
+ [EditorBrowsable(EditorBrowsableState.Advanced)] + public T[] Items => _arr; + + /// + /// Gets a flag indicating if the pool is empty. + /// + /// if the pool is empty; otherwise, . + public bool IsEmpty => _count == 0; + + /// + /// Gets a flag indicating if the pool is full. + /// + /// if the pool is full; otherwise, . + public bool IsFull => _count == Capacity; + + /// + /// Gets a reference to the element at the specified index. + /// + /// The index of the element to access. + /// Thrown if the pool is empty, the index is out of range, or the index is out of bounds. + public ref T this[int index] { + get { + if (IsEmpty) throw new IndexOutOfRangeException("Cannot access an empty pool."); + if (index < 0 || index >= Capacity) throw new IndexOutOfRangeException("Index is out of bounds of the pool."); + if (_free[index]) throw new IndexOutOfRangeException("Index is not allocated in the pool."); + return ref _arr[index]; + } + } + + /// + /// Constructs a new . + /// + /// The allocated capacity of the pool. + public StaticArrayPool(int capacity) { + _arr = new T[capacity]; + _free = new bool[capacity]; + _allocated = new(capacity); + for (int i = capacity - 1; i >= 0; i--) { + ref int index = ref _allocated.PeekPush(); + index = i; + _allocated.Push(); + _free[i] = true; + } + _count = 0; + } + + /// + /// Peeks at the next available slot in the pool. + /// + /// A reference to the next available slot in the pool. + public ref T PeekNext() { + if (IsFull) throw new IndexOutOfRangeException("Cannot peek at next element in a full pool."); + return ref _arr[_allocated.PeekPop()]; + } + + /// + /// Increments the count of allocated elements and + /// returns the index of the newly allocated element. + /// + /// The index of the newly allocated element. + public int Allocate() { + if (IsFull) throw new IndexOutOfRangeException("Cannot allocate elements in a full pool."); + ref int index = ref _allocated.PeekPop(); + _allocated.Pop(); + _free[index] = false; + _count++; + return index; + } + + /// + /// Releases an allocated element in the pool by its index. + /// + /// The index of the element to release. + public void Release(int index) { + if (IsEmpty) throw new IndexOutOfRangeException("Cannot release elements from an empty pool."); + ref int slot = ref _allocated.PeekPush(); + slot = index; + _allocated.Push(); + _free[index] = true; + _count--; + } + + /// + /// Clears the queue by resetting all elements to their defaults. + /// + public void Clear() { + for (int i = 0; i < _arr.Length; i++) { + _arr[i] = default; + _free[i] = true; + } + _allocated.Clear(); + for (int i = _arr.Length - 1; i >= 0; i--) { + ref int index = ref _allocated.PeekPush(); + index = i; + _allocated.Push(); + } + _count = 0; + } + + /// + public IEnumerator GetEnumerator() { + if (IsEmpty) yield break; + if (IsFull) { + for (int i = 0; i < _arr.Length; i++) { + yield return _arr[i]; + } + } else { + for (int i = 0; i < _arr.Length; i++) { + if (!_free[i]) yield return _arr[i]; + } + } + } + + /// + IEnumerator IEnumerable.GetEnumerator() { + return GetEnumerator(); + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Collections/StaticArrayQueue.cs b/CatalystUI/Core/CatalystUI.Collections/StaticArrayQueue.cs new file mode 100644 index 0000000..7716578 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Collections/StaticArrayQueue.cs @@ -0,0 +1,211 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; + +namespace Catalyst.Collections { + + /// + /// A fixed-size queue implementation with zero memory + /// allocations after initialization. + /// + /// + /// + /// Allocations should be made using the method, + /// which returns a reference to the next available slot in the queue. Once + /// updated, the method should be fired to increment + /// the write counter by one. + /// + /// + /// Similarly, the method returns a reference to the + /// front-most enqueued slot in the queue. Once read, the + /// method should be called to increment the read counter by one. + /// + /// + /// The type of elements stored in the queue. + public sealed class StaticArrayQueue : IReadOnlyCollection where T : struct { + + /// + /// The array used to store the queue elements. + /// + private readonly T[] _arr; + + /// + /// The next index to write into the queue. + /// + private int _write; + + /// + /// The next index to read from the queue. + /// + private int _read; + + /// + /// The current number of elements in the queue. + /// + private int _count; + + /// + /// Gets the current number of elements in the queue. + /// + /// The queue's current element count. + public int Count => _count; + + /// + /// Gets the total allocated capacity of the queue. + /// + /// The queue's total allocated capacity. + public int Capacity => _arr.Length; + + /// + /// Gets the underlying array of the queue. + /// + /// + /// + /// INTERNAL USE ONLY. + ///
+ /// Version consistency is not guaranteed, + /// and the property may be changed without notice. + ///
+ /// + /// Warning: Fetching the underlying array + /// provides access to the raw data of the queue, meaning + /// the return value will provide both valid and invalid + /// instances of the underlying struct type. Prefer + /// index-based access via the indexer + /// or enumeration via whenever + /// possible. This property is provided for advanced use cases + /// and/or disposal of the underlying structures, and should + /// be used with special care, attention, and caution. + /// + ///
+ [EditorBrowsable(EditorBrowsableState.Advanced)] + public T[] Items => _arr; + + /// + /// Gets a flag indicating if the queue is empty. + /// + /// if the queue is empty; otherwise, . + public bool IsEmpty => _count == 0; + + /// + /// Gets a flag indicating if the queue is full. + /// + /// if the queue is full; otherwise, . + public bool IsFull => _count == Capacity; + + /// + /// Gets a reference to the element at the specified index. + /// + /// The index of the element to access. + /// Thrown if the queue is empty, the index is out of range, or the index is out of bounds. + public ref T this[int index] { + get { + if (IsEmpty) throw new IndexOutOfRangeException("Cannot access an empty queue."); + if (index < 0 || index >= _count) throw new IndexOutOfRangeException("Index is out of bounds of the queue."); + int adjustedIndex = (_read + index) % _arr.Length; + return ref _arr[adjustedIndex]; + } + } + + /// + /// Constructs a new . + /// + /// The allocated capacity of the queue. + public StaticArrayQueue(int capacity) { + if (capacity < 0) throw new ArgumentOutOfRangeException(nameof(capacity), "Capacity must be a number greater than zero!"); + _arr = new T[capacity]; + _write = 0; + _read = 0; + _count = 0; + } + + /// + /// Peeks at the next available slot in the queue. + /// + /// A reference to the next available slot in the queue. + /// Thrown if the queue is full. + public ref T PeekEnqueue() { + if (IsFull) throw new IndexOutOfRangeException("Cannot peek the back of a full queue."); + return ref _arr[_write]; + } + + /// + /// Increments the write counter by one. + /// + /// Thrown if the queue is full. + public void Enqueue() { + if (IsFull) throw new IndexOutOfRangeException("Cannot enqueue to a full queue."); + _write = (_write + 1) % _arr.Length; + _count++; + } + + /// + /// Peeks at the front-most element in the queue. + /// + /// A reference to the front-most element in the queue. + /// Thrown if the queue is empty. + public ref T PeekDequeue() { + if (IsEmpty) throw new IndexOutOfRangeException("Cannot peek the front of an empty queue."); + return ref _arr[_read]; + } + + /// + /// Increments the read counter by one. + /// + /// Thrown if the queue is empty. + public void Dequeue() { + if (IsEmpty) throw new IndexOutOfRangeException("Cannot dequeue from an empty queue."); + _read = (_read + 1) % _arr.Length; + _count--; + } + + /// + /// Clears the queue by resetting all values to their defaults. + /// + public void Clear() { + for (int i = 0; i < _arr.Length; i++) { + ref T item = ref _arr[i]; + item = default; + } + _write = 0; + _read = 0; + _count = 0; + } + + /// + public IEnumerator GetEnumerator() { + if (IsEmpty) yield break; + if (IsFull) { + for (int i = 0; i < _arr.Length; i++) { + yield return _arr[i]; + } + } else { + int remaining = _count; + int index = _read; + for (int i = 0; i < remaining; i++) { + yield return _arr[index]; + index = (index + 1) % _arr.Length; + } + } + } + + /// + IEnumerator IEnumerable.GetEnumerator() { + return GetEnumerator(); + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Collections/StaticArrayStack.cs b/CatalystUI/Core/CatalystUI.Collections/StaticArrayStack.cs new file mode 100644 index 0000000..3b8e3e2 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Collections/StaticArrayStack.cs @@ -0,0 +1,182 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Catalyst.Collections { + + /// + /// A fixed-size stack implementation with zero memory + /// allocations after initialization. + /// + /// + /// + /// Allocations should be made using the method, + /// which returns a reference to the next available slot in the stack. Once + /// updated, the method should be fired to increment + /// the count by one. + /// + /// + /// Similarly, the method returns a reference to the + /// top-most element in the stack. Once read, the + /// method should be called to decrement the count by one. + /// + /// + /// The type of elements stored in the stack. + public sealed class StaticArrayStack : IReadOnlyCollection where T : struct { + + /// + /// The array used to store the stack elements. + /// + private readonly T[] _arr; + + /// + /// The current number of elements in the stack. + /// + private int _count; + + /// + /// Gets the current number of elements in the stack. + /// + /// The stack's current element count. + public int Count => _count; + + /// + /// Gets the total allocated capacity of the stack. + /// + /// The stack's total allocated capacity. + public int Capacity => _arr.Length; + + /// + /// Gets the underlying array of the stack. + /// + /// + /// + /// INTERNAL USE ONLY. + ///
+ /// Version consistency is not guaranteed, + /// and the property may be changed without notice. + ///
+ /// + /// Warning: Fetching the underlying array + /// provides access to the raw data of the stack, meaning + /// the return value will provide both valid and invalid + /// instances of the underlying struct type. Prefer + /// enumeration via whenever + /// possible. This property is provided for advanced use cases + /// and/or disposal of the underlying structures, and should + /// be used with special care, attention, and caution. + /// + ///
+ public T[] Items => _arr; + + /// + /// Gets a flag indicating whether the stack is empty. + /// + /// if the stack is empty; otherwise, . + public bool IsEmpty => _count == 0; + + /// + /// Gets a flag indicating whether the stack is full. + /// + /// if the stack is full; otherwise, . + public bool IsFull => _count == _arr.Length; + + /// + /// Gets a reference to the element at the specified index. + /// + /// The index of the element to access. + /// Thrown if the stack is empty, the index is out of range, or the index is out of bounds. + public ref T this[int index] { + get { + if (IsEmpty) throw new IndexOutOfRangeException("Cannot access an empty stack."); + if (index >= _count) throw new IndexOutOfRangeException("Index is out of bounds of the stack."); + return ref _arr[index]; + } + } + + /// + /// Constructs a new . + /// + /// The allocated capacity of the stack. + public StaticArrayStack(int capacity) { + if (capacity < 0) throw new ArgumentOutOfRangeException(nameof(capacity), "Capacity must be a number greater than zero!"); + _arr = new T[capacity]; + _count = 0; + } + + /// + /// Peeks at the next available slot in the stack. + /// + /// A reference to the next available slot in the stack. + /// Thrown if the stack is full. + public ref T PeekPush() { + if (IsFull) throw new IndexOutOfRangeException("Stack is full, cannot peek next element!"); + return ref _arr[_count]; + } + + /// + /// Increments the head counter by one. + /// + /// Thrown if the stack is full. + public void Push() { + if (IsFull) throw new IndexOutOfRangeException("Stack is full, cannot push new element!"); + _count++; + } + + /// + /// Peeks at the top element of the stack. + /// + /// A reference to the top element of the stack. + /// Thrown if the stack is empty. + public ref T PeekPop() { + if (IsEmpty) throw new IndexOutOfRangeException("Stack is empty, cannot peek top element!"); + return ref _arr[_count - 1]; + } + + /// + /// Decrements the head counter by one. + /// + /// Thrown if the stack is empty. + public void Pop() { + if (IsEmpty) throw new IndexOutOfRangeException("Stack is empty, cannot pop top element!"); + _count--; + } + + /// + /// Clears the stack by resetting all values to their defaults. + /// + public void Clear() { + for (int i = 0; i < _count; i++) { + ref T item = ref _arr[i]; + item = default; + } + _count = 0; + } + + /// + public IEnumerator GetEnumerator() { + if (IsEmpty) yield break; + for (int i = _count - 1; i >= 0; i--) { + yield return _arr[i]; + } + } + + /// + IEnumerator IEnumerable.GetEnumerator() { + return GetEnumerator(); + } + + } + +} \ No newline at end of file From a4250c831419479839fb518509846a2aeabae21b Mon Sep 17 00:00:00 2001 From: FireController#1847 Date: Sat, 25 Oct 2025 14:04:56 -0600 Subject: [PATCH 08/20] Add Threading Projects * Imported the threading and supporting projects from previous project iterations, including: * Core/CatalystUI.Threading * Core/CatalystUI.Attributes * Tooling/CatalystUI.Analyzers * Tooling/CatalystUI.CodeFix * Bumped all versions to beta.2. --- .scripts/Setup.ps1 | 4 + CatalystUI/CatalystUI.sln | 28 + .../CatalystUI.Attributes.csproj | 32 + .../Threading/CachedDelegateAttribute.cs | 43 + .../CatalystUI.Core/CatalystUI.Core.csproj | 7 + .../CatalystUI.Threading.csproj | 24 + .../CatalystUI.Threading/DelegateQueue.cs | 384 +++++++++ .../RequiresMainThreadException.cs | 44 + .../ThreadDelegateDispatcher.cs | 760 ++++++++++++++++++ .../AnalyzerReleases.Shipped.md | 10 + .../AnalyzerReleases.Unshipped.md | 0 .../CatalystUI.Analyzers.csproj | 58 ++ .../CatalystUI.Analyzers/Descriptors.cs | 75 ++ .../Threading/CachedDelegateGenerator.cs | 162 ++++ .../ThreadDelegateDispatcherAnalyzer.cs | 72 ++ .../CatalystUI.CodeFix.csproj | 52 ++ .../CachedDelegateCodeFixProvider.cs | 162 ++++ ...ThreadDelegateDispatcherCodeFixProvider.cs | 108 +++ 18 files changed, 2025 insertions(+) create mode 100644 CatalystUI/Core/CatalystUI.Attributes/CatalystUI.Attributes.csproj create mode 100644 CatalystUI/Core/CatalystUI.Attributes/Threading/CachedDelegateAttribute.cs create mode 100644 CatalystUI/Core/CatalystUI.Threading/CatalystUI.Threading.csproj create mode 100644 CatalystUI/Core/CatalystUI.Threading/DelegateQueue.cs create mode 100644 CatalystUI/Core/CatalystUI.Threading/RequiresMainThreadException.cs create mode 100644 CatalystUI/Core/CatalystUI.Threading/ThreadDelegateDispatcher.cs create mode 100644 CatalystUI/Tooling/CatalystUI.Analyzers/AnalyzerReleases.Shipped.md create mode 100644 CatalystUI/Tooling/CatalystUI.Analyzers/AnalyzerReleases.Unshipped.md create mode 100644 CatalystUI/Tooling/CatalystUI.Analyzers/CatalystUI.Analyzers.csproj create mode 100644 CatalystUI/Tooling/CatalystUI.Analyzers/Descriptors.cs create mode 100644 CatalystUI/Tooling/CatalystUI.Analyzers/Threading/CachedDelegateGenerator.cs create mode 100644 CatalystUI/Tooling/CatalystUI.Analyzers/Threading/ThreadDelegateDispatcherAnalyzer.cs create mode 100644 CatalystUI/Tooling/CatalystUI.CodeFix/CatalystUI.CodeFix.csproj create mode 100644 CatalystUI/Tooling/CatalystUI.CodeFix/Threading/CachedDelegateCodeFixProvider.cs create mode 100644 CatalystUI/Tooling/CatalystUI.CodeFix/Threading/ThreadDelegateDispatcherCodeFixProvider.cs diff --git a/.scripts/Setup.ps1 b/.scripts/Setup.ps1 index 50cef9e..4bfa893 100644 --- a/.scripts/Setup.ps1 +++ b/.scripts/Setup.ps1 @@ -43,7 +43,11 @@ $projectsList = @( @{ Module = "Core" Projects = @( + @{ Folder = "Core"; Name = "CatalystUI.Attributes" }, @{ Folder = "Core"; Name = "CatalystUI.Collections" }, + @{ Folder = "Core"; Name = "CatalystUI.Threading" }, + @{ Folder = "Tooling"; Name = "CatalystUI.Analyzers" }, + @{ Folder = "Tooling"; Name = "CatalystUI.CodeFix" }, @{ Folder = "Core"; Name = "CatalystUI.Core" } ) PromptIgnore = $true diff --git a/CatalystUI/CatalystUI.sln b/CatalystUI/CatalystUI.sln index c5576fe..956b7d8 100644 --- a/CatalystUI/CatalystUI.sln +++ b/CatalystUI/CatalystUI.sln @@ -10,6 +10,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CatalystUI.Profiling", "Too EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CatalystUI.Collections", "Core\CatalystUI.Collections\CatalystUI.Collections.csproj", "{9B36BF4B-52A9-4881-8D01-391627D51AB9}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CatalystUI.Threading", "Core\CatalystUI.Threading\CatalystUI.Threading.csproj", "{BFC8674D-AE56-4FF4-94B1-ACF5D6B2A4FA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CatalystUI.Analyzers", "Tooling\CatalystUI.Analyzers\CatalystUI.Analyzers.csproj", "{A3936CB7-DC31-414B-9E40-CB9436391068}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CatalystUI.Attributes", "Core\CatalystUI.Attributes\CatalystUI.Attributes.csproj", "{44E8E3D2-FE47-49EA-A397-EB680E33AA2E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CatalystUI.CodeFix", "Tooling\CatalystUI.CodeFix\CatalystUI.CodeFix.csproj", "{E5319DB6-E93C-4A7D-9B3B-F219206BBC54}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -28,10 +36,30 @@ Global {9B36BF4B-52A9-4881-8D01-391627D51AB9}.Debug|Any CPU.Build.0 = Debug|Any CPU {9B36BF4B-52A9-4881-8D01-391627D51AB9}.Release|Any CPU.ActiveCfg = Release|Any CPU {9B36BF4B-52A9-4881-8D01-391627D51AB9}.Release|Any CPU.Build.0 = Release|Any CPU + {BFC8674D-AE56-4FF4-94B1-ACF5D6B2A4FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BFC8674D-AE56-4FF4-94B1-ACF5D6B2A4FA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BFC8674D-AE56-4FF4-94B1-ACF5D6B2A4FA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BFC8674D-AE56-4FF4-94B1-ACF5D6B2A4FA}.Release|Any CPU.Build.0 = Release|Any CPU + {A3936CB7-DC31-414B-9E40-CB9436391068}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A3936CB7-DC31-414B-9E40-CB9436391068}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A3936CB7-DC31-414B-9E40-CB9436391068}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A3936CB7-DC31-414B-9E40-CB9436391068}.Release|Any CPU.Build.0 = Release|Any CPU + {44E8E3D2-FE47-49EA-A397-EB680E33AA2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {44E8E3D2-FE47-49EA-A397-EB680E33AA2E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {44E8E3D2-FE47-49EA-A397-EB680E33AA2E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {44E8E3D2-FE47-49EA-A397-EB680E33AA2E}.Release|Any CPU.Build.0 = Release|Any CPU + {E5319DB6-E93C-4A7D-9B3B-F219206BBC54}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E5319DB6-E93C-4A7D-9B3B-F219206BBC54}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E5319DB6-E93C-4A7D-9B3B-F219206BBC54}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E5319DB6-E93C-4A7D-9B3B-F219206BBC54}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {68F496AC-9438-40F1-9DF8-97363033D661} = {7EC51871-49A8-4991-BDAF-F43A4E9B8C9D} {10856DCF-AD1F-45C2-B995-E36CA4F8751B} = {5D38F696-8C11-4C9A-B50E-2C33AA7FAA6C} {9B36BF4B-52A9-4881-8D01-391627D51AB9} = {7EC51871-49A8-4991-BDAF-F43A4E9B8C9D} + {BFC8674D-AE56-4FF4-94B1-ACF5D6B2A4FA} = {7EC51871-49A8-4991-BDAF-F43A4E9B8C9D} + {A3936CB7-DC31-414B-9E40-CB9436391068} = {5D38F696-8C11-4C9A-B50E-2C33AA7FAA6C} + {44E8E3D2-FE47-49EA-A397-EB680E33AA2E} = {7EC51871-49A8-4991-BDAF-F43A4E9B8C9D} + {E5319DB6-E93C-4A7D-9B3B-F219206BBC54} = {5D38F696-8C11-4C9A-B50E-2C33AA7FAA6C} EndGlobalSection EndGlobal diff --git a/CatalystUI/Core/CatalystUI.Attributes/CatalystUI.Attributes.csproj b/CatalystUI/Core/CatalystUI.Attributes/CatalystUI.Attributes.csproj new file mode 100644 index 0000000..0e818fa --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Attributes/CatalystUI.Attributes.csproj @@ -0,0 +1,32 @@ + + + + + + Catalyst.Attributes + Catalyst.Attributes + + + CatalystUI Attributes + 1.0.0 + beta.2 + FireController#1847 + Attributes API provided by the CatalystUI library. + CatalystUI,attributes + + + + + netstandard2.0 + latest + + + + + false + false + false + none + + + \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Attributes/Threading/CachedDelegateAttribute.cs b/CatalystUI/Core/CatalystUI.Attributes/Threading/CachedDelegateAttribute.cs new file mode 100644 index 0000000..282cbac --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Attributes/Threading/CachedDelegateAttribute.cs @@ -0,0 +1,43 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System; + +namespace Catalyst.Attributes.Threading { + + /// + /// Generates a readonly, cached reference to the annotated method as a delegate. + /// + /// + /// + /// The name for the cached delegate field is prefixed with either _cachedAction or _cachedFunction, + /// depending on whether the method returns void or a value. + /// + /// + /// Valid signatures include: + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + [AttributeUsage(AttributeTargets.Method, Inherited = false)] + public sealed class CachedDelegateAttribute : Attribute { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Core/CatalystUI.Core.csproj b/CatalystUI/Core/CatalystUI.Core/CatalystUI.Core.csproj index 7676c3e..d5d1269 100644 --- a/CatalystUI/Core/CatalystUI.Core/CatalystUI.Core.csproj +++ b/CatalystUI/Core/CatalystUI.Core/CatalystUI.Core.csproj @@ -16,5 +16,12 @@ + + + + + + + \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Threading/CatalystUI.Threading.csproj b/CatalystUI/Core/CatalystUI.Threading/CatalystUI.Threading.csproj new file mode 100644 index 0000000..522eb67 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Threading/CatalystUI.Threading.csproj @@ -0,0 +1,24 @@ + + + + + + Catalyst.Threading + Catalyst.Threading + true + + + CatalystUI Threading + 1.0.0 + beta.2 + FireController#1847 + Threading API provided by the CatalystUI library. + CatalystUI,threading + + + + + + + + \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Threading/DelegateQueue.cs b/CatalystUI/Core/CatalystUI.Threading/DelegateQueue.cs new file mode 100644 index 0000000..f495e98 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Threading/DelegateQueue.cs @@ -0,0 +1,384 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Collections; +using System; +using System.Collections.Generic; +using System.Runtime.ExceptionServices; +using System.Threading; + +namespace Catalyst.Threading { + + /// + /// A thread-safe which enqueues and dequeues delegates for execution. + /// + public sealed class DelegateQueue : IDisposable { + + /// + /// An event handler delegate for events related to the . + /// + public delegate void DelegateQueueEventHandler(DelegateQueue queue, EnqueuedDelegate @delegate); + + /// + /// Invoked when a delegate is enqueued but prior to its execution. + /// + /// + /// Invoked on the calling thread, not the executing thread. + ///

+ /// During invocation, the queue lock is held, so avoid long-running + /// operations or deadlocks may occur. + ///
+ public event DelegateQueueEventHandler? DelegateEnqueued; + + /// + /// Invoked when a delegate is dequeued for execution but prior to its execution. + /// + /// + /// Invoked on the executing thread, not the calling thread. + ///

+ /// During invocation, the queue lock is held, so avoid long-running + /// operations or deadlocks may occur. + ///
+ public event DelegateQueueEventHandler? DelegateDequeued; + + /// + /// Invoked after a delegate has been executed. + /// + /// + /// Invoked on the executing thread, not the calling thread. + ///

+ /// During invocation, the queue lock is held, so avoid long-running + /// operations or deadlocks may occur. + ///
+ public event DelegateQueueEventHandler? DelegateExecuted; + + /// + /// The queue which is used to store enqueued delegates. + /// + private readonly StaticArrayQueue _queue; + + /// + /// A wait handle that can be used to block until the queue is not full. + /// + private readonly ManualResetEvent _queueFullWaitHandle; + + /// + /// The ID of the thread currently executing the queue. + /// + private int _executingThreadId; + + /// + /// A flag indicating whether the object has been disposed of. + /// + private volatile bool _disposed; + + /// + /// A lock used to ensure thread-safe access to the object. + /// + private readonly Lock _lock; + + /// + /// Gets a read-only collection for the underlying queue of enqueued delegates. + /// + /// The underlying queue of enqueued delegates. + public IReadOnlyCollection Queue => _queue; + + /// + /// Constructs a new . + /// + /// The size of the queue, or to use the default value of . + public DelegateQueue(int? size = null) { + size ??= Environment.ProcessorCount; + + // Fields + _queue = new(size.Value); + _queueFullWaitHandle = new(false); + _executingThreadId = -1; + _disposed = false; + _lock = new(); + } + + /// + /// Disposes of the . + /// + ~DelegateQueue() { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: false); + } + + /// + /// Attempts to enqueue a delegate for execution, and + /// waits for it to complete if specified. + /// + /// The delegate to enqueue. + /// A pointer to the caller of the delegate. + /// A pointer to the parameters of the delegate. + /// A pointer to the return value of the delegate. + /// Whether to wait for the delegate to complete execution. + /// The timeout in milliseconds to wait for the delegate to complete execution, or (-1) to wait indefinitely. + /// if the delegate executed within the timeout period; otherwise, . + public bool Enqueue(Delegate @delegate, nint caller, nint parameters, out nint @return, bool wait = false, int timeout = -1) { + ObjectDisposedException.ThrowIf(_disposed, this); + + // Enter the lock + _lock.Enter(); + + // Avoid recursive episodes + if (Environment.CurrentManagedThreadId == _executingThreadId) { + try { + throw new InvalidOperationException($"Enqueuing a delegate from the same-thread (recursion) when a delegate is currently being executed in a {nameof(DelegateQueue)} is not allowed!"); + } finally { + _lock.Exit(); + } + } + + // Wait if the queue is full + while (_queue.IsFull) { + _queueFullWaitHandle.Reset(); + _lock.Exit(); // exit the lock to allow other threads to enqueue + if (!_queueFullWaitHandle.WaitOne(timeout)) { + throw new TimeoutException("The delegate queue is full and the operation timed out."); + } + _lock.Enter(); // re-enter the lock after waiting + } + + // Fetch and enqueue the delegate + ref EnqueuedDelegate entry = ref _queue.PeekEnqueue(); + entry.Delegate = @delegate; + entry.Caller = caller; + entry.Parameters = parameters; + entry.Return = nint.Zero; + entry.Exception = null; + if (wait) { + if (entry.WaitHandle == null) { + entry.WaitHandle = new(false); + } else { + entry.WaitHandle.Reset(); + } + } + entry.HasAwaiter = wait; + _queue.Enqueue(); + + // Why would we not exit the lock here? + // Because we're still in the process of enqueuing, + // and even in the nanosecond timestamp of checking + // the boolean wait and comparing the timeout, + // I have run into race conditions where the + // delegate is executed before the lock is exited, + // resulting in a deadlock. Yeah. Figure that one out. + // ... I had to and it took me days. + + // Wait for the delegate to complete if specified + bool result = true; + if (wait) { + if (timeout != Timeout.Infinite) { + OnDelegateEnqueued(entry); + _lock.Exit(); // exit the lock + result = entry.WaitHandle!.WaitOne(timeout); + } else { + OnDelegateEnqueued(entry); + _lock.Exit(); // exit the lock + entry.WaitHandle!.WaitOne(); // wait indefinitely + } + @return = entry.Return; + if (entry.Exception != null) ExceptionDispatchInfo.Capture(entry.Exception).Throw(); + } else { + OnDelegateEnqueued(entry); + _lock.Exit(); // exit the lock + @return = nint.Zero; + } + return result; + } + + /// + /// Loops through the queue and executes all enqueued delegates. + /// + public void Execute() { + ObjectDisposedException.ThrowIf(_disposed, this); + _lock.Enter(); + try { + // If there's nothing to execute, return immediately + if (_queue.IsEmpty) return; + + // Loop through the queue and execute each delegate + _executingThreadId = Environment.CurrentManagedThreadId; + while (_queue.Count != 0) { + ref EnqueuedDelegate entry = ref _queue.PeekDequeue(); + OnDelegateDequeued(entry); + _lock.Exit(); // leave the lock for delegate execution + try { + entry.Execute(); + } finally { + _lock.Enter(); // re-enter the lock after execution + OnDelegateExecuted(entry); + // if we disposed of ourselves or the queue while executing, + // it could have become empty, so we check again + if (!_disposed && !_queue.IsEmpty) { + entry.WaitHandle?.Set(); + _queue.Dequeue(); + } + } + if (_disposed) return; // if we disposed of ourselves while executing, exit the loop + } + + // Cleanup executing state + _queueFullWaitHandle.Set(); // signal that the queue is not full anymore + _executingThreadId = -1; // reset the executing thread ID + } finally { + _lock.Exit(); // finally exit the lock + } + } + + /// + private void OnDelegateEnqueued(EnqueuedDelegate @delegate) { + DelegateEnqueued?.Invoke(this, @delegate); + } + + /// + private void OnDelegateDequeued(EnqueuedDelegate @delegate) { + DelegateDequeued?.Invoke(this, @delegate); + } + + /// + private void OnDelegateExecuted(EnqueuedDelegate @delegate) { + DelegateExecuted?.Invoke(this, @delegate); + } + + /// + /// Disposes of the . + /// + public void Dispose() { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + /// if disposal is being performed by the garbage collector, otherwise + /// + private void Dispose(bool disposing) { + _lock.Enter(); + try { + if (_disposed) return; + + // Dispose managed state (managed objects) + if (disposing) { + // Dispose of the underlying wait handles + EnqueuedDelegate[] items = _queue.Items; + for (int i = 0; i < items.Length; i++) { + ref EnqueuedDelegate entry = ref items[i]; + entry.WaitHandle?.Dispose(); + } + _queue.Clear(); + + // Dispose the queue full wait handle + _queueFullWaitHandle.Dispose(); + } + + // Dispose unmanaged state (unmanaged objects) + // ... + + // Indicate disposal completion + _disposed = true; + } finally { + _lock.Exit(); + } + } + + /// + /// Represents a delegate which has been enqueued for execution. + /// + public record struct EnqueuedDelegate { + + /// + /// Gets or sets the delegate to be executed. + /// + /// The delegate to execute. + public required Delegate Delegate { get; set; } + + /// + /// Gets or sets a pointer to the caller of the delegate. + /// + /// The delegate's caller poiner. + public required nint Caller { get; set; } + + /// + /// Gets or sets a pointer to the parameters of the delegate. + /// + /// The delegate's parameters pointer. + public required nint Parameters { get; set; } + + /// + /// Gets or sets a pointer to the return value of the delegate. + /// + /// The delegate's return pointer. + public required nint Return { get; set; } + + /// + /// Gets or sets an exception that occurred during execution of the delegate, if any. + /// + /// The exception that occurred during execution, or if no exception occurred. + public required Exception? Exception { get; set; } + + /// + /// Gets or sets a wait handle that can be used to wait for the delegate to complete execution. + /// + /// The wait handle for the delegate. + public required ManualResetEvent? WaitHandle { get; set; } + + /// + /// Gets or sets a flag indicating whether the delegate has an awaiter. + /// + /// if the delegate has an awaiter; otherwise, . + public required bool HasAwaiter { get; set; } + + /// + /// Executes the enqueued delegate. + /// + public void Execute() { + try { + switch (Delegate) { + case Action action: + action(); + break; + case Action action2: + action2(Caller); + break; + case Action action3: + action3(Caller, Parameters); + break; + case Func func: + Return = func(); + break; + case Func func2: + Return = func2(Caller); + break; + case Func func3: + Return = func3(Caller, Parameters); + break; + default: + throw new NotSupportedException($"The delegate type {Delegate.GetType()} is not supported by the {nameof(EnqueuedDelegate)} structure."); + } + } catch (Exception e) { + // If we have an awaiter, pass the exception to it + // Otherwise, it's an unhandled exception + if (HasAwaiter) { + Exception = e; + } else { + throw; + } + } + } + + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Threading/RequiresMainThreadException.cs b/CatalystUI/Core/CatalystUI.Threading/RequiresMainThreadException.cs new file mode 100644 index 0000000..aa2a58b --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Threading/RequiresMainThreadException.cs @@ -0,0 +1,44 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System.ComponentModel; + +namespace Catalyst.Threading { + + /// + /// Represents an exception that is thrown + /// when the operation requires the main thread + /// to be captured, but it has not been captured. + /// + public class RequiresMainThreadException : InvalidAsynchronousStateException { + + /// + /// Constructs a new . + /// + /// The name of the class that contains the method requiring the main thread to be captured. + /// The name of the method that requires the main thread to be captured. + public RequiresMainThreadException(string className, string methodName) : base(GetMessage(className, methodName)) { + // ... + } + + /// + /// Gets the message for the exception. + /// + /// The name of the class that contains the method requiring the main thread to be captured. + /// The name of the method that requires the main thread to be captured. + /// A formatted message indicating the method that requires the main thread to be captured. + private static string GetMessage(string className, string methodName) { + return $"The method '{className}#{methodName}' requires the main thread to be captured, but it has not been captured!"; + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Threading/ThreadDelegateDispatcher.cs b/CatalystUI/Core/CatalystUI.Threading/ThreadDelegateDispatcher.cs new file mode 100644 index 0000000..66a3e0f --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Threading/ThreadDelegateDispatcher.cs @@ -0,0 +1,760 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Collections; +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Threading; + +namespace Catalyst.Threading { + + /// + /// Provides delegate execution synchronization for a given thread. + /// + public sealed class ThreadDelegateDispatcher : IDisposable { + + /// + /// An event handler delegate for events related to the . + /// + public delegate void DispatcherEventHandler(ThreadDelegateDispatcher dispatcher); + + /// + /// An event handler delegate for queue events related to the . + /// + public delegate void DispatcherQueueEventHandler(ThreadDelegateDispatcher dispatcher, Delegate @delegate); + + /// + /// Invoked prior to the execution of the delegate queue. + /// + /// + /// Always invoked from the dispatcher's associated thread. + /// + public event DispatcherEventHandler? PreExecute; + + /// + /// Invoked after the execution of the delegate queue. + /// + /// + /// Always invoked from the dispatcher's associated thread. + /// + public event DispatcherEventHandler? PostExecute; + + /// + public event DispatcherQueueEventHandler? DelegateEnqueued; + + /// + public event DispatcherQueueEventHandler? DelegateDequeued; + + /// + public event DispatcherQueueEventHandler? DelegateExecuted; + + /// + /// The default sizing for the delegate caches when using the typed execute methods. + /// + public const int DEFAULT_DELEGATE_CACHE_SIZE = 8; + + /// + /// The maximum number of milliseconds (ms) an enqueued delegate + /// should remain unresponsive before timing out. + /// + /// + /// + /// The timeout is a utility value which can be used by + /// any user of a dispatcher to ensure that their + /// awaited executions do not block indefinitely. + /// It is a commonly suggested value, but is not + /// required to be used, since some delegates may + /// take longer to execute than others. + /// + /// + /// The value is larger when in debug mode to allow + /// for debuggers to attach and inspect the code's + /// execution. In release mode, it is set to a + /// value of 250ms for fast failures. + /// + /// + public static int LockoutTimeout => Debugger.IsAttached ? Timeout.Infinite : 250; + + /// + /// Gets the main thread dispatcher fetched via . + /// + /// The main thread dispatcher, or if it has not been captured. + public static ThreadDelegateDispatcher? MainThreadDispatcher { get; private set; } + + /// + /// Gets a flag indicating if the main thread has been captured. + /// + /// if the main thread has been captured, otherwise . + [MemberNotNullWhen(true, nameof(MainThreadDispatcher))] + public static bool IsMainThreadCaptured => MainThreadDispatcher != null; + + /// + /// The dispatcher's worker thread, or if it is associated with the main thread. + /// + private readonly Thread? _thread; + + /// + /// The ID of the dispatcher's worker thread. + /// + private readonly int _threadId; + + /// + /// The queue of delegates to be executed on the thread. + /// + private readonly DelegateQueue _queue; + + /// + /// A flag indicating whether the object has been disposed of. + /// + private volatile bool _disposed; + + /// + /// A lock used to ensure thread-safe access to the object. + /// + private readonly Lock _lock; + + /// + /// Gets the worker thread of the dispatcher. + /// + /// The dispatcher's worker thread, or if it is associated with the main thread. + public Thread? Thread => _thread; + + /// + /// Gets the managed thread ID of dispatcher's worker thread. + /// + /// The dispatcher's worker thread managed thread ID. + public int ThreadId => _threadId; + + /// + /// Gets the number of delegates currently enqueued in the dispatcher. + /// + public int Enqueued => _queue.Queue.Count; + + /// + /// Constructs a new . + /// + /// The thread to associate with the dispatcher. + /// The managed thread ID of the thread. + /// The size of the delegate queue, or to use the default value of . + private ThreadDelegateDispatcher(Thread thread, int threadId, int? queueSize = null) { + // Fields + _thread = thread; + _threadId = threadId; + _queue = new(queueSize); + _queue.DelegateEnqueued += HandleDelegateEnqueued; + _queue.DelegateDequeued += HandleDelegateDequeued; + _queue.DelegateExecuted += HandleDelegateExecuted; + _disposed = false; + _lock = new(); + } + + /// + /// Constructs a new + /// by creating a new background worker thread with the + /// specified name. + /// + /// + /// To end the execution of the dispatcher, simply + /// remove all references to it and ensure no + /// further delegates are enqueued. The newly + /// created worker thread is marked as a background + /// thread. You can also call the + /// method on the dispatcher to immediately + /// end execution, although this is not + /// encouraged as it could result in undefined + /// behavior if the thread is still in the process + /// of executing an enqueued delegate. + /// + /// The name of the thread to be created, or to use the default thread name. + /// The size of the delegate queue, or to use the default value of . + /// A new instance of . + public static ThreadDelegateDispatcher New(string? threadName = null, int? queueSize = null) { + ThreadDelegateDispatcher? dispatcher = null; + + // ReSharper disable AccessToDisposedClosure + // ReSharper disable AccessToModifiedClosure + // ReSharper disable LoopVariableIsNeverChangedInsideLoop + // Construct a new worker thread prior to constructing the dispatcher + using ManualResetEvent spinUpWaitHandle = new(false); + Thread thread = new(() => { + spinUpWaitHandle.Set(); + while (dispatcher == null) { + Thread.SpinWait(1); + Thread.Yield(); + } + dispatcher.WorkerThread(); + }) { + Name = threadName, IsBackground = true + }; + // ReSharper restore LoopVariableIsNeverChangedInsideLoop + // ReSharper restore AccessToDisposedClosure + // ReSharper restore AccessToModifiedClosure + + // Construct the dispatcher, start the thread, and return + dispatcher = new(thread, thread.ManagedThreadId, queueSize); + thread.Start(); + spinUpWaitHandle.WaitOne(); // wait for the thread to spin up + return dispatcher; + } + + /// + /// Constructs a new + /// by capturing the current thread and enqueuing the specified + /// callback as the first delegate to be executed. + /// + /// + /// To end execution of the dispatcher, execute the + /// method somewhere within + /// the callback. If the captured thread is not marked + /// as a background thread, such as the main thread, + /// the application will not close until the dispatcher + /// is disposed of. To instead create a new background + /// worker thread, prefer the + /// method. + /// + /// The callback to be executed on the thread. + /// The size of the delegate queue, or to use the default value of . + /// Whether the thread to be captured is the main thread. If , the dispatcher will be set as the main thread dispatcher. + public static void Capture(Action callback, int? queueSize = null, bool isMainThread = false) { + if (isMainThread && MainThreadDispatcher != null) throw new InvalidOperationException("The main thread dispatcher has already been captured!"); + ThreadDelegateDispatcher dispatcher = new( + Thread.CurrentThread, + Environment.CurrentManagedThreadId, + queueSize + ); + if (isMainThread) MainThreadDispatcher = dispatcher; + // manually enqueue to prevent our smart thread id checking from just calling it directly + dispatcher._queue.Enqueue(() => callback(dispatcher), nint.Zero, nint.Zero, out _); + dispatcher.WorkerThread(); + } + + /// + /// Disposes of the . + /// + ~ThreadDelegateDispatcher() { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: false); + } + + /// + public bool Execute(in Action @delegate, TCaller caller, TParameters parameters, bool wait = false, int timeout = -2) { + ObjectDisposedException.ThrowIf(_disposed, this); + if (timeout == -2) timeout = LockoutTimeout; + ActionCache.Lock.Enter(); + bool shouldUnlock = true; + try { + if (Environment.CurrentManagedThreadId == _threadId) { + @delegate(caller, parameters); + return true; + } else { + // Request a context from the pool + StaticArrayPool.Context> pool = ActionCache.Pool; + ref ActionCache.Context ctx = ref pool.PeekNext(); + + // Assign the context + ctx._delegate = @delegate; + ctx._caller = caller; + ctx._parameters = parameters; + + // Allocate context in the pool + int index = pool.Allocate(); + + // Enqueue and return + if (wait) { + shouldUnlock = false; + ActionCache.Lock.Exit(); + } + + return _queue.Enqueue( + ActionCache.Action, + index, + nint.Zero, + out nint _, + wait, + timeout + ); + } + } finally { + if (shouldUnlock) ActionCache.Lock.Exit(); + } + } + + /// + public bool Execute(in Action @delegate, TCaller caller, bool wait = false, int timeout = -2) { + ObjectDisposedException.ThrowIf(_disposed, this); + if (timeout == -2) timeout = LockoutTimeout; + ActionCache.Lock.Enter(); + bool shouldUnlock = true; + try { + if (Environment.CurrentManagedThreadId == _threadId) { + @delegate(caller); + + return true; + } else { + // Request a context from the pool + StaticArrayPool.Context> pool = ActionCache.Pool; + ref ActionCache.Context ctx = ref pool.PeekNext(); + + // Assign the context + ctx._delegate = @delegate; + ctx._caller = caller; + + // Allocate context in the pool + int index = pool.Allocate(); + + // Enqueue and return + if (wait) { + shouldUnlock = false; + ActionCache.Lock.Exit(); + } + + return _queue.Enqueue( + ActionCache.Action, + index, + nint.Zero, + out nint _, + wait, + timeout + ); + } + } finally { + if (shouldUnlock) ActionCache.Lock.Exit(); + } + } + + /// + public bool Execute(in Action @delegate, nint caller, nint parameters, bool wait = false, int timeout = -2) { + ObjectDisposedException.ThrowIf(_disposed, this); + if (timeout == -2) timeout = LockoutTimeout; + if (Environment.CurrentManagedThreadId == _threadId) { + @delegate(caller, parameters); + return true; + } else { + return _queue.Enqueue(@delegate, caller, parameters, out _, wait, timeout); + } + } + + /// + public bool Execute(in Action @delegate, nint caller, bool wait = false, int timeout = -2) { + ObjectDisposedException.ThrowIf(_disposed, this); + if (timeout == -2) timeout = LockoutTimeout; + if (Environment.CurrentManagedThreadId == _threadId) { + @delegate(caller); + return true; + } else { + return _queue.Enqueue(@delegate, caller, nint.Zero, out _, wait, timeout); + } + } + + /// + public bool Execute(in Action @delegate, bool wait = false, int timeout = -2) { + ObjectDisposedException.ThrowIf(_disposed, this); + if (timeout == -2) timeout = LockoutTimeout; + if (Environment.CurrentManagedThreadId == _threadId) { + @delegate(); + return true; + } else { + return _queue.Enqueue(@delegate, nint.Zero, nint.Zero, out _, wait, timeout); + } + } + + /// + public bool Execute(in Func @delegate, TCaller caller, TParameters parameters, out TReturn @return, int timeout = -2) { + ObjectDisposedException.ThrowIf(_disposed, this); + if (timeout == -2) timeout = LockoutTimeout; + FunctionCache.Lock.Enter(); + bool shouldUnlock = true; + try { + if (Environment.CurrentManagedThreadId == _threadId) { + @return = @delegate(caller, parameters); + return true; + } else { + // Request a context from the pool + StaticArrayPool.Context> pool = FunctionCache.Pool; + ref FunctionCache.Context ctx = ref pool.PeekNext(); + + // Assign the context + ctx._delegate = @delegate; + ctx._caller = caller; + ctx._parameters = parameters; + ctx._return = default!; + + // Allocate context in the pool + int index = pool.Allocate(); + shouldUnlock = false; + FunctionCache.Lock.Exit(); + bool success = _queue.Enqueue( + FunctionCache.Function, + index, + nint.Zero, + out nint _, + true, + timeout + ); + shouldUnlock = true; + FunctionCache.Lock.Enter(); + @return = ctx._return; + return success; + } + } finally { + if (shouldUnlock) FunctionCache.Lock.Exit(); + } + } + + /// + public bool Execute(in Func @delegate, TCaller caller, out TReturn @return, int timeout = -2) { + ObjectDisposedException.ThrowIf(_disposed, this); + if (timeout == -2) timeout = LockoutTimeout; + FunctionCache.Lock.Enter(); + bool shouldUnlock = true; + try { + if (Environment.CurrentManagedThreadId == _threadId) { + @return = @delegate(caller); + + return true; + } else { + // Request a context from the pool + StaticArrayPool.Context> pool = FunctionCache.Pool; + ref FunctionCache.Context ctx = ref pool.PeekNext(); + + // Assign the context + ctx._delegate = @delegate; + ctx._caller = caller; + ctx._return = default!; + + // Allocate context in the pool + int index = pool.Allocate(); + shouldUnlock = false; + FunctionCache.Lock.Exit(); + bool success = _queue.Enqueue( + FunctionCache.Function, + index, + nint.Zero, + out nint _, + true, + timeout + ); + shouldUnlock = true; + FunctionCache.Lock.Enter(); + @return = ctx._return; + return success; + } + } finally { + if (shouldUnlock) FunctionCache.Lock.Exit(); + } + } + + /// + public bool Execute(in Func @delegate, out TReturn @return, int timeout = -2) { + ObjectDisposedException.ThrowIf(_disposed, this); + if (timeout == -2) timeout = LockoutTimeout; + FunctionCache.Lock.Enter(); + bool shouldUnlock = true; + try { + if (Environment.CurrentManagedThreadId == _threadId) { + @return = @delegate(); + return true; + } else { + // Request a context from the pool + StaticArrayPool.Context> pool = FunctionCache.Pool; + ref FunctionCache.Context ctx = ref pool.PeekNext(); + + // Assign the context + ctx._delegate = @delegate; + ctx._return = default!; + + // Allocate context in the pool + int index = pool.Allocate(); + shouldUnlock = false; + FunctionCache.Lock.Exit(); + bool success = _queue.Enqueue( + FunctionCache.Function, + index, + nint.Zero, + out nint _, + true, + timeout + ); + shouldUnlock = true; + FunctionCache.Lock.Enter(); + @return = ctx._return; + return success; + } + } finally { + if (shouldUnlock) FunctionCache.Lock.Exit(); + } + } + + /// + public bool Execute(in Func @delegate, nint caller, nint parameters, out nint @return, int timeout = -2) { + ObjectDisposedException.ThrowIf(_disposed, this); + if (timeout == -2) timeout = LockoutTimeout; + if (Environment.CurrentManagedThreadId == _threadId) { + @return = @delegate(caller, parameters); + return true; + } else { + return _queue.Enqueue(@delegate, caller, parameters, out @return, true, timeout); + } + } + + /// + public bool Execute(in Func @delegate, nint caller, out nint @return, int timeout = -2) { + ObjectDisposedException.ThrowIf(_disposed, this); + if (timeout == -2) timeout = LockoutTimeout; + if (Environment.CurrentManagedThreadId == _threadId) { + @return = @delegate(caller); + return true; + } else { + return _queue.Enqueue(@delegate, caller, nint.Zero, out @return, true, timeout); + } + } + + /// + public bool Execute(in Func @delegate, out nint @return, int timeout = -2) { + ObjectDisposedException.ThrowIf(_disposed, this); + if (timeout == -2) timeout = LockoutTimeout; + if (Environment.CurrentManagedThreadId == _threadId) { + @return = @delegate(); + return true; + } else { + return _queue.Enqueue(@delegate, nint.Zero, nint.Zero, out @return, true, timeout); + } + } + + /// + private void HandleDelegateEnqueued(DelegateQueue queue, DelegateQueue.EnqueuedDelegate @delegate) { + OnDelegateEnqueued(@delegate.Delegate); + } + + /// + private void HandleDelegateDequeued(DelegateQueue queue, DelegateQueue.EnqueuedDelegate @delegate) { + OnDelegateDequeued(@delegate.Delegate); + } + + /// + private void HandleDelegateExecuted(DelegateQueue queue, DelegateQueue.EnqueuedDelegate @delegate) { + OnDelegateExecuted(@delegate.Delegate); + } + + /// + private void OnPreExecute() { + PreExecute?.Invoke(this); + } + + /// + private void OnPostExecute() { + PostExecute?.Invoke(this); + } + + /// + private void OnDelegateEnqueued(Delegate @delegate) { + DelegateEnqueued?.Invoke(this, @delegate); + } + + /// + private void OnDelegateDequeued(Delegate @delegate) { + DelegateDequeued?.Invoke(this, @delegate); + } + + /// + private void OnDelegateExecuted(Delegate @delegate) { + DelegateExecuted?.Invoke(this, @delegate); + } + + /// + /// The worker thread method that processes the delegate queue. + /// + private void WorkerThread() { + while (true) { + _lock.Enter(); + try { + if (_disposed) break; + OnPreExecute(); + if (_disposed) break; + _queue.Execute(); + if (_disposed) break; + OnPostExecute(); + } finally { + _lock.Exit(); + } + } + } + + /// + /// Disposes of the . + /// + public void Dispose() { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + /// if disposal is being performed by the garbage collector, otherwise + /// + private void Dispose(bool disposing) { + _lock.Enter(); + try { + if (_disposed) return; + + // Dispose managed state (managed objects) + if (disposing) { + _queue.DelegateEnqueued -= HandleDelegateEnqueued; + _queue.DelegateDequeued -= HandleDelegateDequeued; + _queue.DelegateExecuted -= HandleDelegateExecuted; + _queue.Dispose(); + } + + // Dispose unmanaged state (unmanaged objects) + // ... + + // Indicate disposal completion + _disposed = true; + } finally { + _lock.Exit(); + } + } + +#pragma warning disable + // ReSharper disable All + internal static class ActionCache { + + internal static readonly Lock Lock = new(); + internal static readonly StaticArrayPool Pool = new(DEFAULT_DELEGATE_CACHE_SIZE); + + internal static readonly Action Action = (poolIndex, _) => { + ref Context ctx = ref Pool[(int) poolIndex]; + try { + ctx.Execute(); + } finally { + Lock.Enter(); + try { + Pool.Release((int) poolIndex); + } finally { + Lock.Exit(); + } + } + }; + + internal struct Context { + + internal Action _delegate; + internal TCaller _caller; + internal TParameters _parameters; + internal void Execute() => _delegate(_caller, _parameters); + + } + + } + + internal static class ActionCache { + + internal static readonly Lock Lock = new(); + internal static readonly StaticArrayPool Pool = new(DEFAULT_DELEGATE_CACHE_SIZE); + + internal static readonly Action Action = poolIndex => { + ref Context ctx = ref Pool[(int) poolIndex]; + try { + ctx.Execute(); + } finally { + Lock.Enter(); + try { + Pool.Release((int) poolIndex); + } finally { + Lock.Exit(); + } + } + }; + + internal struct Context { + + internal Action _delegate; + internal TCaller _caller; + internal void Execute() => _delegate(_caller); + + } + + } + + internal static class FunctionCache { + + internal static readonly Lock Lock = new(); + internal static readonly StaticArrayPool Pool = new(DEFAULT_DELEGATE_CACHE_SIZE); + + internal static readonly Func Function = (poolIndex, _) => { + ref Context ctx = ref Pool[(int) poolIndex]; + ctx.Execute(); + + return nint.Zero; + }; + + internal struct Context { + + internal Func _delegate; + internal TCaller _caller; + internal TParameters _parameters; + internal TReturn _return; + internal void Execute() => _return = _delegate(_caller, _parameters); + + } + + } + + internal static class FunctionCache { + + internal static readonly Lock Lock = new(); + internal static readonly StaticArrayPool Pool = new(DEFAULT_DELEGATE_CACHE_SIZE); + + internal static readonly Func Function = (poolIndex, _) => { + ref Context ctx = ref Pool[(int) poolIndex]; + ctx.Execute(); + + return nint.Zero; + }; + + internal struct Context { + + internal Func _delegate; + internal TCaller _caller; + internal TReturn _return; + internal void Execute() => _return = _delegate(_caller); + + } + + } + + internal static class FunctionCache { + + internal static readonly Lock Lock = new(); + internal static readonly StaticArrayPool Pool = new(DEFAULT_DELEGATE_CACHE_SIZE); + + internal static readonly Func Function = (poolIndex, _) => { + ref Context ctx = ref Pool[(int) poolIndex]; + ctx.Execute(); + + return nint.Zero; + }; + + internal struct Context { + + internal Func _delegate; + internal TReturn _return; + internal void Execute() => _return = _delegate(); + + } + + } + // ReSharper restore All +#pragma warning restore + + } + +} \ No newline at end of file diff --git a/CatalystUI/Tooling/CatalystUI.Analyzers/AnalyzerReleases.Shipped.md b/CatalystUI/Tooling/CatalystUI.Analyzers/AnalyzerReleases.Shipped.md new file mode 100644 index 0000000..445db9f --- /dev/null +++ b/CatalystUI/Tooling/CatalystUI.Analyzers/AnalyzerReleases.Shipped.md @@ -0,0 +1,10 @@ +## Release 1.0 + +### New Rules + +Rule ID | Category | Severity | Notes + ---------|----------|----------|---------------------------------------------------- + CTL001 | Usage | Warning | CTL001_Threading_TDDAnalyzer +CTL002 | Usage | Error | CTL002_Threading_Attributes_CacheDelegateAttribute +CTL003 | Usage | Error | CTL003_Threading_Attributes_CacheDelegateAttribute +CTL004 | Usage | Error | CTL003_Threading_Attributes_CacheDelegateAttribute \ No newline at end of file diff --git a/CatalystUI/Tooling/CatalystUI.Analyzers/AnalyzerReleases.Unshipped.md b/CatalystUI/Tooling/CatalystUI.Analyzers/AnalyzerReleases.Unshipped.md new file mode 100644 index 0000000..e69de29 diff --git a/CatalystUI/Tooling/CatalystUI.Analyzers/CatalystUI.Analyzers.csproj b/CatalystUI/Tooling/CatalystUI.Analyzers/CatalystUI.Analyzers.csproj new file mode 100644 index 0000000..3464a15 --- /dev/null +++ b/CatalystUI/Tooling/CatalystUI.Analyzers/CatalystUI.Analyzers.csproj @@ -0,0 +1,58 @@ + + + + + true + + + + + + Catalyst.Analyzers + Catalyst.Analyzers + false + + + CatalystUI Analyzers + 1.0.0 + beta.2 + FireController#1847 + Analyzers API provided by the CatalystUI library. + CatalystUI,analyzers + + + + + + + + + + + netstandard2.0 + latest + + + + + false + false + false + none + + + + + + + + + + + \ No newline at end of file diff --git a/CatalystUI/Tooling/CatalystUI.Analyzers/Descriptors.cs b/CatalystUI/Tooling/CatalystUI.Analyzers/Descriptors.cs new file mode 100644 index 0000000..b585f1c --- /dev/null +++ b/CatalystUI/Tooling/CatalystUI.Analyzers/Descriptors.cs @@ -0,0 +1,75 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Microsoft.CodeAnalysis; + +namespace Catalyst.Analyzers { + + /// + /// Contains all diagnostic descriptors for the CatalystUI analyzers. + /// + public static class Descriptors { + + /// + /// CTL001: Ignored return value of awaited dispatched execution. + /// + public static readonly DiagnosticDescriptor CTL001 = new( + "CTL001", + "Ignored return value of awaited execution", + "The return value of the '{0}' method on '{1}' should not be ignored when the '{2}' parameter is set to true", + "Usage", + DiagnosticSeverity.Warning, + true, + "Ignoring the return value of an awaited execution can lead to unexpected behavior if the execution fails or does not complete as expected. If ignoring the return value is intentional, explicitly ignore it using '_'." + ); + + /// + /// CTL002: Partial class required for cached delegate. + /// + public static readonly DiagnosticDescriptor CTL002 = new( + "CTL002", + "Partial class required for cached delegate", + "Any class containing a method annotated with the '{0}' must be declared as 'partial'", + "Usage", + DiagnosticSeverity.Error, + true, + "Cached delegates require the containing class to be partial to allow the source generator to inject the necessary code." + ); + + /// + /// CTL003: Backing method for cached delegate must be static. + /// + public static readonly DiagnosticDescriptor CTL003 = new( + "CTL003", + "Backing method for cached delegate must be static", + "A method annotated with the '{0}' must be declared as 'static'", + "Usage", + DiagnosticSeverity.Error, + true, + "Cached delegates are designed to be static to ensure they can be accessed without an instance of the class. This allows for efficient caching and execution across threads." + ); + + /// + /// CTL004: Too many parameters for a cached delegate. + /// + public static readonly DiagnosticDescriptor CTL004 = new( + "CTL004", + "Too many parameters for a cached delegate", + "A method annotated with the '{0}' must contain no more than 2 parameters: 'caller' and 'parameters'", + "Usage", + DiagnosticSeverity.Error, + true, + "Dispatched delegates are passed between threads and require minimal data to retain performance and efficiency. Methods annotated with the '{0}' attribute must adhere to this constraint." + ); + + } + +} \ No newline at end of file diff --git a/CatalystUI/Tooling/CatalystUI.Analyzers/Threading/CachedDelegateGenerator.cs b/CatalystUI/Tooling/CatalystUI.Analyzers/Threading/CachedDelegateGenerator.cs new file mode 100644 index 0000000..a0680a5 --- /dev/null +++ b/CatalystUI/Tooling/CatalystUI.Analyzers/Threading/CachedDelegateGenerator.cs @@ -0,0 +1,162 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Attributes.Threading; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System; +using System.Linq; + +namespace Catalyst.Analyzers.Threading { + + /// + /// The source generator for the . + /// + [Generator] + public sealed class CachedDelegateGenerator : IIncrementalGenerator { + + /// + /// Gets the prefix for a cached action field. + /// + private const string CACHED_ACTION_PREFIX = "_cachedAction"; + + /// + /// Gets the prefix for a cached function field. + /// + private const string CACHED_FUNCTION_PREFIX = "_cachedFunction"; + + /// + public void Initialize(IncrementalGeneratorInitializationContext context) { + // Fetch the methods annotated with the CacheDelegateAttribute + IncrementalValuesProvider<(MethodDeclarationSyntax, IMethodSymbol)> methods = + context.SyntaxProvider.ForAttributeWithMetadataName( + $"Catalyst.Attributes.Threading.{nameof(CachedDelegateAttribute)}", + static (node, _) => node is MethodDeclarationSyntax, + static (ctx, _) => ( + (MethodDeclarationSyntax) ctx.TargetNode, + (IMethodSymbol) ctx.SemanticModel.GetDeclaredSymbol(ctx.TargetNode)! + ) + ).Where(ctx => ctx is { Item1: not null, Item2: not null }); + + // Register the source generation for the methods + context.RegisterSourceOutput(methods, GenerateCacheDelegateSource); + } + + /// + /// Generates the source code for the cached delegate. + /// + private static void GenerateCacheDelegateSource(SourceProductionContext context, (MethodDeclarationSyntax, IMethodSymbol) pair) { + MethodDeclarationSyntax methodSyntax = pair.Item1; + IMethodSymbol methodSymbol = pair.Item2; + + // Verify the containing class is partial + INamedTypeSymbol classSymbol = methodSymbol.ContainingType; + if (!classSymbol.DeclaringSyntaxReferences.Any(sr => sr.GetSyntax() is ClassDeclarationSyntax cds && cds.Modifiers.Any(m => m.Text == "partial"))) { + context.ReportDiagnostic(Diagnostic.Create( + Descriptors.CTL002, + methodSyntax.GetLocation(), + nameof(CachedDelegateAttribute) + )); + return; + } + + // Verify the method is static + if (!methodSymbol.IsStatic) { + context.ReportDiagnostic(Diagnostic.Create( + Descriptors.CTL003, + methodSyntax.GetLocation(), + nameof(CachedDelegateAttribute) + )); + return; + } + + // Verify the method does not contain too many parameters + if (methodSymbol.Parameters.Length > 2) { + context.ReportDiagnostic(Diagnostic.Create( + Descriptors.CTL004, + methodSyntax.GetLocation(), + nameof(CachedDelegateAttribute) + )); + return; + } + + // Prepare variables for the namespace and class + string ns = classSymbol.ContainingNamespace.ToDisplayString(); + string classAccess = ConvertAccessibility(classSymbol.DeclaredAccessibility); + string className = classSymbol.Name; + bool classStatic = classSymbol.IsStatic; + + // Prepare variables for the method + string methodAccess = ConvertAccessibility(methodSymbol.DeclaredAccessibility); + string methodName = methodSymbol.Name; + string prefix = methodSymbol.ReturnsVoid ? CACHED_ACTION_PREFIX : CACHED_FUNCTION_PREFIX; + string fieldName = $"{prefix}{methodName}"; + string delegateType = methodSymbol.ReturnsVoid ? + methodSymbol.Parameters.Length switch { + 0 => "Action", + 1 => $"Action<{methodSymbol.Parameters[0].Type.ToDisplayString()}>", + 2 => $"Action<{methodSymbol.Parameters[0].Type.ToDisplayString()}, {methodSymbol.Parameters[1].Type.ToDisplayString()}>", + _ => throw new NotImplementedException() + } : + methodSymbol.Parameters.Length switch { + 0 => $"Func<{methodSymbol.ReturnType.ToDisplayString()}>", + 1 => $"Func<{methodSymbol.Parameters[0].Type.ToDisplayString()}, {methodSymbol.ReturnType.ToDisplayString()}>", + 2 => $"Func<{methodSymbol.Parameters[0].Type.ToDisplayString()}, {methodSymbol.Parameters[1].Type.ToDisplayString()}, {methodSymbol.ReturnType.ToDisplayString()}>", + _ => throw new NotImplementedException() + }; + + // Prepare variables for the comments + string classCref = $"{ns}.{className}"; + string methodCref = $"{ns}.{className}.{methodName}("; + methodCref += string.Join(", ", methodSymbol.Parameters.Select(p => p.Type.ToDisplayString())); + methodCref += ")"; + + // Partial class source code generation + string source = + $$""" + #nullable enable + + // + namespace {{ns}} { + + {{classAccess}} {{(classStatic ? "static " : "")}}partial class {{className}} { + + /// + /// A cached {{(methodSymbol.ReturnsVoid ? "action" : "function")}} for the method . + /// + /// + {{methodAccess}} static {{delegateType}} {{fieldName}} = {{methodName}}; + + } + + } + """; + + // Add the generated source to the compilation + context.AddSource($"{className}_{methodName}_CachedDelegates.g.cs", source); + } + + private static string ConvertAccessibility(Accessibility accessibility) { + return accessibility switch { + Accessibility.Public => "public", + Accessibility.Private => "private", + Accessibility.Protected => "protected", + Accessibility.Internal => "internal", + Accessibility.ProtectedOrInternal => "protected internal", + Accessibility.ProtectedAndInternal => "private protected", + Accessibility.NotApplicable => "", + _ => throw new ArgumentOutOfRangeException(nameof(accessibility), accessibility, null) + }; + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Tooling/CatalystUI.Analyzers/Threading/ThreadDelegateDispatcherAnalyzer.cs b/CatalystUI/Tooling/CatalystUI.Analyzers/Threading/ThreadDelegateDispatcherAnalyzer.cs new file mode 100644 index 0000000..a3fb6a5 --- /dev/null +++ b/CatalystUI/Tooling/CatalystUI.Analyzers/Threading/ThreadDelegateDispatcherAnalyzer.cs @@ -0,0 +1,72 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; +using System.Collections.Immutable; +using System.Linq; + +namespace Catalyst.Analyzers.Threading { + + /// + /// Analyzes use cases for the Thread Delegate Dispatcher (TDD) in CatalystUI. + /// + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class ThreadDelegateDispatcherAnalyzer : DiagnosticAnalyzer { + + /// + public override ImmutableArray SupportedDiagnostics => [ Descriptors.CTL001 ]; + + /// + public override void Initialize(AnalysisContext context) { + context.EnableConcurrentExecution(); + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.RegisterOperationAction(AnalyzeInvocation, OperationKind.Invocation); + } + + /// + /// Analyze invocations of the Thread Delegate Dispatcher (TDD) methods. + /// + /// The operation analysis context. + private static void AnalyzeInvocation(OperationAnalysisContext context) { + IInvocationOperation invocation = (IInvocationOperation) context.Operation; + + // If it is not an expression statement, ignore it + if (invocation.Parent is not IExpressionStatementOperation) return; + + // If it is not our method, ignore it + IMethodSymbol method = invocation.TargetMethod; + if (method.Name != "Execute" || method.ReturnType.SpecialType != SpecialType.System_Boolean) return; + + // If the 'wait' parameter is not true, ignore it + IParameterSymbol? waitParam = method.Parameters.FirstOrDefault(p => p.Name == "wait" && p.Type.SpecialType == SpecialType.System_Boolean); + bool shouldReport = false; + if (waitParam == null) { + shouldReport = true; + } else { + IArgumentOperation? waitArg = invocation.Arguments.FirstOrDefault(arg => SymbolEqualityComparer.Default.Equals(arg.Parameter, waitParam)); + if (waitArg == null) return; + if (waitArg.Value.ConstantValue is { HasValue: true, Value: true }) { + shouldReport = true; + } + } + + // Report diagnostic if the 'wait' parameter is true + if (shouldReport) { + Diagnostic diagnostic = Diagnostic.Create(Descriptors.CTL001, invocation.Syntax.GetLocation(), method.Name, method.ContainingType.ToDisplayString(), "wait"); + context.ReportDiagnostic(diagnostic); + } + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Tooling/CatalystUI.CodeFix/CatalystUI.CodeFix.csproj b/CatalystUI/Tooling/CatalystUI.CodeFix/CatalystUI.CodeFix.csproj new file mode 100644 index 0000000..caa43e6 --- /dev/null +++ b/CatalystUI/Tooling/CatalystUI.CodeFix/CatalystUI.CodeFix.csproj @@ -0,0 +1,52 @@ + + + + + + Catalyst.CodeFix + Catalyst.CodeFix + false + + + CatalystUI CodeFix + 1.0.0 + beta.2 + FireController#1847 + CodeFix API provided by the CatalystUI library. + CatalystUI,codefix + + + + + + + + + + + netstandard2.0 + latest + + + + + false + false + false + none + + + + + + + + + + \ No newline at end of file diff --git a/CatalystUI/Tooling/CatalystUI.CodeFix/Threading/CachedDelegateCodeFixProvider.cs b/CatalystUI/Tooling/CatalystUI.CodeFix/Threading/CachedDelegateCodeFixProvider.cs new file mode 100644 index 0000000..99b6ca8 --- /dev/null +++ b/CatalystUI/Tooling/CatalystUI.CodeFix/Threading/CachedDelegateCodeFixProvider.cs @@ -0,0 +1,162 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Analyzers; +using Catalyst.Attributes.Threading; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Formatting; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Catalyst.CodeFix.Threading { + + /// + /// Provides code fixes for the CacheDelegateAttribute (CDA) source generator. + /// + [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(CachedDelegateCodeFixProvider))] + [Shared] + public class CachedDelegateCodeFixProvider : CodeFixProvider { + + /// + public override ImmutableArray FixableDiagnosticIds => [ + Descriptors.CTL002.Id, + Descriptors.CTL003.Id + ]; + + /// + public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + /// + public override async Task RegisterCodeFixesAsync(CodeFixContext context) { + foreach (Diagnostic diagnostic in context.Diagnostics) { + // CTL002: Partial class required for cached delegate + if (diagnostic.Id == Descriptors.CTL002.Id) { + SyntaxNode? root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + SyntaxNode? node = root?.FindNode(diagnostic.Location.SourceSpan); + if (node == null) continue; + + // Add mark class as partial code fix + context.RegisterCodeFix( + CodeAction.Create( + "Mark class as partial", + ct => MarkClassAsPartialAsync(context.Document, node, ct), + nameof(CachedDelegateCodeFixProvider) + "_MarkPartial" + ), + diagnostic + ); + + // CTL003: Backing method for cached delegate must be static + } else if (diagnostic.Id == Descriptors.CTL003.Id) { + SyntaxNode? root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + SyntaxNode? node = root?.FindNode(diagnostic.Location.SourceSpan); + if (node == null) continue; + + // Add make method static and pass caller code fix + if (node is MethodDeclarationSyntax methodNode && node.AncestorsAndSelf().OfType().FirstOrDefault() is ClassDeclarationSyntax classNode) { + string className = classNode.Identifier.ValueText; + if (methodNode.ParameterList.Parameters.All(p => p.Type?.ToString() != className)) { + context.RegisterCodeFix( + CodeAction.Create( + "Make method static and pass caller", + ct => MakeMethodStaticAndAddCaller(context.Document, node, ct), + nameof(CachedDelegateCodeFixProvider) + "_MakeStaticAndPassCaller" + ), + diagnostic + ); + } + } + + // Add make method static code fix + context.RegisterCodeFix( + CodeAction.Create( + "Make method static", + ct => MakeMethodStaticAsync(context.Document, node, ct), + nameof(CachedDelegateCodeFixProvider) + "_MakeStatic" + ), + diagnostic + ); + } + } + } + + /// + /// Marks the class containing the method annotated with as partial. + /// + private static async Task MarkClassAsPartialAsync(Document document, SyntaxNode node, CancellationToken cancellationToken) { + if (node.AncestorsAndSelf().OfType().FirstOrDefault() is not ClassDeclarationSyntax classNode) return document; + if (classNode.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword))) return document; + + // Add the partial modifier to the class + SyntaxToken partial = SyntaxFactory.Token(SyntaxKind.PartialKeyword).WithTrailingTrivia(SyntaxFactory.Space); + ClassDeclarationSyntax newClassNode = classNode.WithModifiers(classNode.Modifiers.Add(partial)).WithAdditionalAnnotations(Formatter.Annotation); + + // Apply changes to the document + SyntaxNode? root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + SyntaxNode? newRoot = root?.ReplaceNode(classNode, newClassNode); + return newRoot == null ? document : document.WithSyntaxRoot(newRoot); + } + + /// + /// Makes the method annotated with static and adds a caller parameter. + /// + private static async Task MakeMethodStaticAndAddCaller(Document document, SyntaxNode node, CancellationToken cancellationToken) { + if (node is not MethodDeclarationSyntax methodNode) return document; + if (node.AncestorsAndSelf().OfType().FirstOrDefault() is not ClassDeclarationSyntax classNode) return document; + if (methodNode.Modifiers.Any(m => m.IsKind(SyntaxKind.StaticKeyword))) return document; + + // Add the static modifier to the method + SyntaxToken staticModifier = SyntaxFactory.Token(SyntaxKind.StaticKeyword).WithTrailingTrivia(SyntaxFactory.Space); + MethodDeclarationSyntax newMethodNode = methodNode.WithModifiers(methodNode.Modifiers.Add(staticModifier)).WithAdditionalAnnotations(Formatter.Annotation); + + // Add the caller parameter + ParameterSyntax callerParameter = SyntaxFactory.Parameter( + SyntaxFactory.Identifier("caller") + ).WithType(SyntaxFactory.ParseTypeName(classNode.Identifier.Text)).WithDefault(null); + SeparatedSyntaxList parameters = methodNode.ParameterList.Parameters; + parameters = parameters.Insert(0, callerParameter); + newMethodNode = newMethodNode.WithParameterList( + SyntaxFactory.ParameterList(SyntaxFactory.SeparatedList(parameters)) + ); + + // Apply changes to the document + SyntaxNode? root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + SyntaxNode? newRoot = root?.ReplaceNode(methodNode, newMethodNode); + return newRoot == null ? document : document.WithSyntaxRoot(newRoot); + } + + /// + /// Makes the method annotated with static. + /// + private static async Task MakeMethodStaticAsync(Document document, SyntaxNode node, CancellationToken cancellationToken) { + if (node is not MethodDeclarationSyntax methodNode) return document; + if (node.AncestorsAndSelf().OfType().FirstOrDefault() is not ClassDeclarationSyntax classNode) return document; + if (methodNode.Modifiers.Any(m => m.IsKind(SyntaxKind.StaticKeyword))) return document; + + // Add the static modifier to the method + SyntaxToken staticModifier = SyntaxFactory.Token(SyntaxKind.StaticKeyword).WithTrailingTrivia(SyntaxFactory.Space); + MethodDeclarationSyntax newMethodNode = methodNode.WithModifiers(methodNode.Modifiers.Add(staticModifier)).WithAdditionalAnnotations(Formatter.Annotation); + + // Apply changes to the document + SyntaxNode? root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + SyntaxNode? newRoot = root?.ReplaceNode(methodNode, newMethodNode); + return newRoot == null ? document : document.WithSyntaxRoot(newRoot); + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Tooling/CatalystUI.CodeFix/Threading/ThreadDelegateDispatcherCodeFixProvider.cs b/CatalystUI/Tooling/CatalystUI.CodeFix/Threading/ThreadDelegateDispatcherCodeFixProvider.cs new file mode 100644 index 0000000..238c7f8 --- /dev/null +++ b/CatalystUI/Tooling/CatalystUI.CodeFix/Threading/ThreadDelegateDispatcherCodeFixProvider.cs @@ -0,0 +1,108 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Analyzers; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Formatting; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Catalyst.CodeFix.Threading { + + /// + /// Provides code fixes for the Thread Delegate Dispatcher (TDD) analyzer. + /// + [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(ThreadDelegateDispatcherCodeFixProvider))] + [Shared] + public class ThreadDelegateDispatcherCodeFixProvider : CodeFixProvider { + + /// + public override ImmutableArray FixableDiagnosticIds => [ Descriptors.CTL001.Id ]; + + /// + public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + /// + public override async Task RegisterCodeFixesAsync(CodeFixContext context) { + Diagnostic diagnostic = context.Diagnostics.First(); + SyntaxNode? root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + SyntaxNode? node = root?.FindNode(diagnostic.Location.SourceSpan); + if (node == null) return; + + // Add check against return value code fix + context.RegisterCodeFix( + CodeAction.Create( + "Check against return value", + c => CheckAgainstReturnValueAsync(context.Document, node, c), + nameof(ThreadDelegateDispatcherCodeFixProvider) + ), + diagnostic + ); + + // Add discard assignment code fix + context.RegisterCodeFix( + CodeAction.Create( + "Assign result to discard", + c => AddDiscardAssignmentAsync(context.Document, node, c), + nameof(ThreadDelegateDispatcherCodeFixProvider) + ), + diagnostic + ); + } + + private static async Task CheckAgainstReturnValueAsync(Document document, SyntaxNode node, CancellationToken cancellationToken) { + if (node is not InvocationExpressionSyntax { Parent: ExpressionStatementSyntax expression } invocation) return document; + PrefixUnaryExpressionSyntax negated = SyntaxFactory.PrefixUnaryExpression( + SyntaxKind.LogicalNotExpression, + invocation.WithoutTrailingTrivia() + ); + ThrowStatementSyntax throwStatement = SyntaxFactory.ThrowStatement( + SyntaxFactory.ObjectCreationExpression( + SyntaxFactory.IdentifierName("NotImplementedException") + ).WithArgumentList(SyntaxFactory.ArgumentList()) + ).WithAdditionalAnnotations(new SyntaxAnnotation("CaretTarget")) + .WithTrailingTrivia(); + BlockSyntax block = SyntaxFactory.Block(throwStatement); + IfStatementSyntax ifStatement = SyntaxFactory.IfStatement(negated, block) + .WithTriviaFrom(expression) + .WithAdditionalAnnotations(Formatter.Annotation); + SyntaxNode? root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + SyntaxNode? newRoot = root?.ReplaceNode(expression, ifStatement); + return newRoot == null ? document : document.WithSyntaxRoot(newRoot); + } + + /// + /// Assigns the result of a thread delegate dispatcher call to a discard variable. + /// + private static async Task AddDiscardAssignmentAsync(Document document, SyntaxNode node, CancellationToken cancellationToken) { + if (node is not InvocationExpressionSyntax invocation) return document; + ExpressionStatementSyntax newExpression = SyntaxFactory.ExpressionStatement( + SyntaxFactory.AssignmentExpression( + SyntaxKind.SimpleAssignmentExpression, + SyntaxFactory.IdentifierName("_"), + invocation + ) + ).WithTriviaFrom(invocation.Parent!); + SyntaxNode? root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + SyntaxNode? newRoot = root?.ReplaceNode(invocation.Parent!, newExpression); + return newRoot == null ? document : document.WithSyntaxRoot(newRoot); + } + + } + +} \ No newline at end of file From c73b058fbbcfd294e2e5fce3069bcdc8f476ab55 Mon Sep 17 00:00:00 2001 From: FireController#1847 Date: Sat, 25 Oct 2025 14:07:36 -0600 Subject: [PATCH 09/20] Update project authors * Updated all project authors from "FireController#1847" to "CatalystUI LLC" --- .../Core/CatalystUI.Attributes/CatalystUI.Attributes.csproj | 2 +- .../Core/CatalystUI.Collections/CatalystUI.Collections.csproj | 2 +- CatalystUI/Core/CatalystUI.Core/CatalystUI.Core.csproj | 2 +- .../Core/CatalystUI.Threading/CatalystUI.Threading.csproj | 2 +- .../Tooling/CatalystUI.Analyzers/CatalystUI.Analyzers.csproj | 2 +- CatalystUI/Tooling/CatalystUI.CodeFix/CatalystUI.CodeFix.csproj | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CatalystUI/Core/CatalystUI.Attributes/CatalystUI.Attributes.csproj b/CatalystUI/Core/CatalystUI.Attributes/CatalystUI.Attributes.csproj index 0e818fa..f1c9654 100644 --- a/CatalystUI/Core/CatalystUI.Attributes/CatalystUI.Attributes.csproj +++ b/CatalystUI/Core/CatalystUI.Attributes/CatalystUI.Attributes.csproj @@ -10,7 +10,7 @@ CatalystUI Attributes 1.0.0 beta.2 - FireController#1847 + CatalystUI LLC Attributes API provided by the CatalystUI library. CatalystUI,attributes diff --git a/CatalystUI/Core/CatalystUI.Collections/CatalystUI.Collections.csproj b/CatalystUI/Core/CatalystUI.Collections/CatalystUI.Collections.csproj index e440558..111d7d0 100644 --- a/CatalystUI/Core/CatalystUI.Collections/CatalystUI.Collections.csproj +++ b/CatalystUI/Core/CatalystUI.Collections/CatalystUI.Collections.csproj @@ -10,7 +10,7 @@ CatalystUI Collections 1.0.0 beta.2 - FireController#1847 + CatalystUI LLC Collections API provided by the CatalystUI library. CatalystUI,collections diff --git a/CatalystUI/Core/CatalystUI.Core/CatalystUI.Core.csproj b/CatalystUI/Core/CatalystUI.Core/CatalystUI.Core.csproj index d5d1269..339ec99 100644 --- a/CatalystUI/Core/CatalystUI.Core/CatalystUI.Core.csproj +++ b/CatalystUI/Core/CatalystUI.Core/CatalystUI.Core.csproj @@ -10,7 +10,7 @@ CatalystUI Core 1.0.0 beta.2 - FireController#1847 + CatalystUI LLC Core API provided by the CatalystUI library. CatalystUI,core diff --git a/CatalystUI/Core/CatalystUI.Threading/CatalystUI.Threading.csproj b/CatalystUI/Core/CatalystUI.Threading/CatalystUI.Threading.csproj index 522eb67..11aef9a 100644 --- a/CatalystUI/Core/CatalystUI.Threading/CatalystUI.Threading.csproj +++ b/CatalystUI/Core/CatalystUI.Threading/CatalystUI.Threading.csproj @@ -11,7 +11,7 @@ CatalystUI Threading 1.0.0 beta.2 - FireController#1847 + CatalystUI LLC Threading API provided by the CatalystUI library. CatalystUI,threading diff --git a/CatalystUI/Tooling/CatalystUI.Analyzers/CatalystUI.Analyzers.csproj b/CatalystUI/Tooling/CatalystUI.Analyzers/CatalystUI.Analyzers.csproj index 3464a15..76b2b0f 100644 --- a/CatalystUI/Tooling/CatalystUI.Analyzers/CatalystUI.Analyzers.csproj +++ b/CatalystUI/Tooling/CatalystUI.Analyzers/CatalystUI.Analyzers.csproj @@ -16,7 +16,7 @@ CatalystUI Analyzers 1.0.0 beta.2 - FireController#1847 + CatalystUI LLC Analyzers API provided by the CatalystUI library. CatalystUI,analyzers diff --git a/CatalystUI/Tooling/CatalystUI.CodeFix/CatalystUI.CodeFix.csproj b/CatalystUI/Tooling/CatalystUI.CodeFix/CatalystUI.CodeFix.csproj index caa43e6..b31334c 100644 --- a/CatalystUI/Tooling/CatalystUI.CodeFix/CatalystUI.CodeFix.csproj +++ b/CatalystUI/Tooling/CatalystUI.CodeFix/CatalystUI.CodeFix.csproj @@ -11,7 +11,7 @@ CatalystUI CodeFix 1.0.0 beta.2 - FireController#1847 + CatalystUI LLC CodeFix API provided by the CatalystUI library. CatalystUI,codefix From 0dbc4f90b94d6089ce58f67f182985b42b775a5d Mon Sep 17 00:00:00 2001 From: FireController#1847 Date: Sat, 25 Oct 2025 14:08:38 -0600 Subject: [PATCH 10/20] Update workflow to include more branches --- .github/workflows/dotnet.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index ad9bf76..c8bf401 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -7,9 +7,9 @@ permissions: on: push: - branches: [ "main" ] + branches: [ "main", "beta", "alpha" ] pull_request: - branches: [ "main" ] + branches: [ "main", "beta", "alpha" ] jobs: build: From 9c516baa405f8a217e1ebdda85a695256e21a21b Mon Sep 17 00:00:00 2001 From: FireController#1847 Date: Sat, 25 Oct 2025 14:15:45 -0600 Subject: [PATCH 11/20] Update CatalystUI/Core/CatalystUI.Threading/DelegateQueue.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: FireController#1847 --- CatalystUI/Core/CatalystUI.Threading/DelegateQueue.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CatalystUI/Core/CatalystUI.Threading/DelegateQueue.cs b/CatalystUI/Core/CatalystUI.Threading/DelegateQueue.cs index f495e98..f448e02 100644 --- a/CatalystUI/Core/CatalystUI.Threading/DelegateQueue.cs +++ b/CatalystUI/Core/CatalystUI.Threading/DelegateQueue.cs @@ -306,7 +306,7 @@ public record struct EnqueuedDelegate { /// /// Gets or sets a pointer to the caller of the delegate. /// - /// The delegate's caller poiner. + /// The delegate's caller pointer. public required nint Caller { get; set; } /// From c1b0561386a62d4415348350ff22543c46c03fa4 Mon Sep 17 00:00:00 2001 From: FireController#1847 Date: Sat, 25 Oct 2025 14:22:41 -0600 Subject: [PATCH 12/20] Resolve build failure * Temporarily remove dependency on Catalyst.Debug for the Tooling/CatalystUI.Profiling project, since that has not yet been re-added. --- .../Tooling/CatalystUI.Profiling/CatalystUI.Profiling.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CatalystUI/Tooling/CatalystUI.Profiling/CatalystUI.Profiling.csproj b/CatalystUI/Tooling/CatalystUI.Profiling/CatalystUI.Profiling.csproj index 36ca046..5da9d12 100644 --- a/CatalystUI/Tooling/CatalystUI.Profiling/CatalystUI.Profiling.csproj +++ b/CatalystUI/Tooling/CatalystUI.Profiling/CatalystUI.Profiling.csproj @@ -10,7 +10,7 @@ - + From ae7d94950a8f112cf9a7d167bec6befca9fba7e3 Mon Sep 17 00:00:00 2001 From: FireController#1847 Date: Sat, 25 Oct 2025 14:26:54 -0600 Subject: [PATCH 13/20] Update CodeQL workflow to include additional branches Signed-off-by: FireController#1847 --- .github/workflows/codeql.yml | 99 ++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..a65df8a --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,99 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL Advanced" + +on: + push: + branches: [ "main", "beta", "alpha" ] + pull_request: + branches: [ "main", "beta", "alpha" ] + schedule: + - cron: '42 23 * * 3' + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners (GitHub.com only) + # Consider using larger runners or machines with greater resources for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + permissions: + # required for all workflows + security-events: write + + # required to fetch internal or private CodeQL packs + packages: read + + # only required for workflows in private repositories + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + include: + - language: actions + build-mode: none + # CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift' + # Use `c-cpp` to analyze code written in C, C++ or both + # Use 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, + # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. + # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how + # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Add any setup steps before running the `github/codeql-action/init` action. + # This includes steps like installing compilers or runtimes (`actions/setup-node` + # or others). This is typically only required for manual builds. + # - name: Setup runtime (example) + # uses: actions/setup-example@v1 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v4 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + # If the analyze step fails for one of the languages you are analyzing with + # "We were unable to automatically build your code", modify the matrix above + # to set the build mode to "manual" for that language. Then modify this step + # to build your code. + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + - name: Run manual build steps + if: matrix.build-mode == 'manual' + shell: bash + run: | + echo 'If you are using a "manual" build mode for one or more of the' \ + 'languages you are analyzing, replace this with the commands to build' \ + 'your code, for example:' + echo ' make bootstrap' + echo ' make release' + exit 1 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v4 + with: + category: "/language:${{matrix.language}}" From 606eec73566cbff9389563ebf2937a101b6d3a98 Mon Sep 17 00:00:00 2001 From: FireController#1847 Date: Sat, 25 Oct 2025 21:39:25 -0600 Subject: [PATCH 14/20] Add builder pattern classes * Imported the builder pattern and associated classes from previous project iterations. * Imported the model registry and native and associated classes from previous project iterations. * Fixed incorrect import in Core/CatalystUI.Core * Added debugging statements to the model registry. --- .../Builders/CatalystAppBuilder.cs | 146 ++++++++++ .../CatalystAppBuilderExtensions.cs | 26 ++ .../Core/CatalystUI.Core/CatalystApp.cs | 109 +++++++ .../CatalystUI.Core/CatalystUI.Core.csproj | 2 +- .../Debugging/CatalystDebug.cs | 6 +- .../Core/CatalystUI.Core/ModelRegistry.cs | 272 ++++++++++++++++++ .../Core/CatalystUI.Core/Native/INativeApi.cs | 37 +++ .../CatalystUI.Core/Native/NativeException.cs | 48 ++++ .../CatalystUI.Profiling.csproj | 1 + .../Properties/catdebug.template.ini | 1 + 10 files changed, 644 insertions(+), 4 deletions(-) create mode 100644 CatalystUI/Core/CatalystUI.Core/Builders/CatalystAppBuilder.cs create mode 100644 CatalystUI/Core/CatalystUI.Core/Builders/Extensions/CatalystAppBuilderExtensions.cs create mode 100644 CatalystUI/Core/CatalystUI.Core/CatalystApp.cs create mode 100644 CatalystUI/Core/CatalystUI.Core/ModelRegistry.cs create mode 100644 CatalystUI/Core/CatalystUI.Core/Native/INativeApi.cs create mode 100644 CatalystUI/Core/CatalystUI.Core/Native/NativeException.cs diff --git a/CatalystUI/Core/CatalystUI.Core/Builders/CatalystAppBuilder.cs b/CatalystUI/Core/CatalystUI.Core/Builders/CatalystAppBuilder.cs new file mode 100644 index 0000000..5216e92 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/Builders/CatalystAppBuilder.cs @@ -0,0 +1,146 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Connectors; +using Catalyst.Domains; +using Catalyst.Layers; +using System; +using System.ComponentModel; +using System.Threading.Tasks; + +namespace Catalyst.Builders { + + /// + /// Builder class for creating a Catalyst application. + /// + public sealed class CatalystAppBuilder { + + /// + /// Constructs a new . + /// + public CatalystAppBuilder() { + // ... + } + + /// + /// Makes a layer available for use in the CatalystUI application. + /// + /// + /// + /// The added layer is registered with the , + /// which allows it to be used globally across the application. + /// + /// + /// Layers are the building blocks of the CatalystUI application, + /// providing a way to organize and manage different parts of the application. + /// Inserting a layer into the application allows it to add additional functionality + /// such as data handling, UI components, or other features. To connect the functionality + /// of multiple layers, you can use connectors. + /// + /// + /// + /// The layer to add. + /// The type of the layer to add. + /// The current instance of the . + public CatalystAppBuilder AddLayer(TLayer layer) where TLayer : ILayer { + ModelRegistry.RegisterLayer(layer); + return this; + } + + /// + /// Makes a connector available for use in the CatalystUI application. + /// + /// + /// + /// The added connector is registered with the , + /// which allows it to be used globally across the application. + /// + /// + /// Connectors allow different layers to communicate with each other, + /// and for their underlying domains to interact seamlessly. To add + /// new functionality to the application, you can insert a layer + /// and connect it to existing layers using connectors. + /// + /// + /// + /// The connector to add. + /// The type of the connector to add. + /// The current instance of the . + public CatalystAppBuilder AddConnector(TConnector connector) where TConnector : IConnector, ILayer> { + ModelRegistry.RegisterConnector(connector); + return this; + } + + /// + /// Builds the CatalystUI Application. + /// + /// + /// + /// Building a Catalyst application will capture the thread on which it is called. + /// This thread should be the main thread of the application to ensure proper operation. + /// Failure to do so may result in unexpected behavior and occasional crashes. + /// + /// + /// The resulting run method will be executed on a separate thread. If the caller needs + /// to perform work on the main thread, it should use the + /// to schedule work on the main thread. + /// + /// + /// The application will not exit until the run method completes or the method is called. + /// To keep an application running indefinitely, you would do as you would in a typical + /// application, such as using a loop or waiting for user input. + /// + /// + /// The method to run when the application starts. + /// A new instance of a CatalystUI application. + public void Build(Func runMethod) { + _ = new CatalystApp(runMethod); + } + + /// + public void Build(Action runMethod) { + _ = new CatalystApp(app => { + runMethod(app); + return 0; // Default exit code + }); + } + + /// + public void Build(Func runMethod) { + _ = new CatalystApp(app => { + runMethod(app).GetAwaiter().GetResult(); + return 0; // Default exit code + }); + } + + /// + public void Build(Func> runMethod) { + _ = new CatalystApp(app => runMethod(app).GetAwaiter().GetResult()); + } + + /// + /// When building a Catalyst application, it is recommended to use the overloads that accept a or as the run method, + /// which allows the application to capture the main thread and prevent issues during asynchronous operations. + /// This method is provided for advanced use cases only and should be used with caution, + /// as some functionality may cause unexpected behavior if the main thread is not captured correctly. + /// MacOS and Linux seem to be particularly sensitive to this, as they require the main thread to be + /// captured for proper system-level operations and UI interactions. + /// + /// + [Obsolete("Use Build(Action) or Build(Func) whenever possible. This method is provided for advanced use cases only.")] + [EditorBrowsable(EditorBrowsableState.Advanced)] + public void Build() { + _ = new CatalystApp(); + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Core/Builders/Extensions/CatalystAppBuilderExtensions.cs b/CatalystUI/Core/CatalystUI.Core/Builders/Extensions/CatalystAppBuilderExtensions.cs new file mode 100644 index 0000000..09fdba0 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/Builders/Extensions/CatalystAppBuilderExtensions.cs @@ -0,0 +1,26 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +namespace Catalyst.Builders.Extensions { + + /// + /// This doesn't provide any functionality itself, + /// but it provides the namespace for extension methods, + /// so conditional compilation doesn't yell at end-users + /// if they need it during a debug build but not a release build. + /// + public static class CatalystAppBuilderExtensions { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Core/CatalystApp.cs b/CatalystUI/Core/CatalystUI.Core/CatalystApp.cs new file mode 100644 index 0000000..c58b100 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/CatalystApp.cs @@ -0,0 +1,109 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Debugging; +using Catalyst.Threading; +using System; +using System.Threading; + +namespace Catalyst { + + /// + /// An application utilizing the CatalystUI framework. + /// + public class CatalystApp { + + /// + /// The dispatcher used to handle threading operations in the CatalystUI framework. + /// + /// + /// In most situations, the dispatcher will be the one used to capture the main thread of the application. + /// A and associated should + /// always be created on the main thread of the application. Failure to do so may result in + /// unexpected behavior and occasional crashes. + /// + public ThreadDelegateDispatcher Dispatcher { get; private set; } = null!; // set in the captured main thread + + /// + /// The debug context for the application. + /// + protected readonly DebugContext _debug; + + /// + /// The exit code of the application. + /// + private volatile int _exitCode; + + /// + /// A flag indicating whether the application is exiting. + /// + private volatile bool _isExiting; + + /// + /// A lock used to ensure thread safety when performing Catalyst + /// synchronizational operations, such as exiting the application. + /// + private readonly Lock _lock; + + /// + /// Constructs a new . + /// This method will not return. + /// + internal CatalystApp(Func? runMethod = null) { + // Fields + _debug = CatalystDebug.ForContext("Application"); + _exitCode = 0; + _isExiting = false; + _lock = new(); + + // Properties + _debug.Log(LogLevel.Verbose, "Capturing main thread dispatcher and running application..."); + ThreadDelegateDispatcher.Capture(dispatcher => { + // Assign the main-thread dispatcher to the app. + Dispatcher = dispatcher; + Thread.CurrentThread.Name = "MainThread"; + _debug.Log(LogLevel.Verbose, "Main thread dispatcher captured successfully."); + + // Run the caller's method if provided. + if (runMethod != null) { + _debug.Log(LogLevel.Info, "CatalystApp initialized, initializing worker thread. Application will run until the provided run method exits."); + ThreadDelegateDispatcher workerThread = ThreadDelegateDispatcher.New("CallerThread"); + workerThread.Execute(() => { + Exit(runMethod(this)); + }); + } else { + _debug.Log(LogLevel.Info, "CatalystApp initialized. No run method provided, application will run until exit."); + } + }, isMainThread: true); + + // Set the exit code and let the application return gracefully. + Environment.ExitCode = _exitCode; + } + + /// + /// Kills the main-thread dispatcher and exits the application with the specified exit code. + /// + /// The exit code to use when exiting the application. + public void Exit(int code = 0) { + if (_isExiting) return; + _lock.Enter(); + try { + _exitCode = code; + Dispatcher.Dispose(); + _isExiting = true; + } finally { + _lock.Exit(); + } + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Core/CatalystUI.Core.csproj b/CatalystUI/Core/CatalystUI.Core/CatalystUI.Core.csproj index 339ec99..81d5241 100644 --- a/CatalystUI/Core/CatalystUI.Core/CatalystUI.Core.csproj +++ b/CatalystUI/Core/CatalystUI.Core/CatalystUI.Core.csproj @@ -19,7 +19,7 @@ - + diff --git a/CatalystUI/Core/CatalystUI.Core/Debugging/CatalystDebug.cs b/CatalystUI/Core/CatalystUI.Core/Debugging/CatalystDebug.cs index e4d4f8b..d3dec4b 100644 --- a/CatalystUI/Core/CatalystUI.Core/Debugging/CatalystDebug.cs +++ b/CatalystUI/Core/CatalystUI.Core/Debugging/CatalystDebug.cs @@ -317,12 +317,12 @@ public static LogLevel DetermineScopeLevel(string scope) { level = parsedResult.Value; _enabledScopes.TryAdd(scope, level); } else { - Trace.WriteLine($"⚠️ Scope '{scope}' is not defined in the configuration file. Using default level: {DebugOptions.MinimumLogLevel}"); + Trace.WriteLine($"⚠️ [Catalyst.Debugging] Scope '{scope}' is not defined in the configuration file. Using default level: {DebugOptions.MinimumLogLevel}"); } // Scope output if (DebugOptions.MinimumLogLevel >= LogLevel.Debug) { - Trace.WriteLine($"ℹ️ Determined scope '{scope}' level: {level}"); + Trace.WriteLine($"ℹ️ [Catalyst.Debugging] Determined scope '{scope}' level: {level}"); } } return level; @@ -362,7 +362,7 @@ private static void PreloadScopes() { /// A function that constructs a new . public static void InjectDebugContext(Func constructor) { if (_debugContextConstructor != null) { - Trace.WriteLine("⚠️ A debug constructor has already been set. The previous constructor will be replaced with the new one."); + Trace.WriteLine("⚠️ [Catalyst.Debugging] A debug constructor has already been set. The previous constructor will be replaced with the new one."); Trace.WriteLine(constructor.GetType().GetGenericArguments()[1].FullName); } _debugContextConstructor = constructor; diff --git a/CatalystUI/Core/CatalystUI.Core/ModelRegistry.cs b/CatalystUI/Core/CatalystUI.Core/ModelRegistry.cs new file mode 100644 index 0000000..e1f987b --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/ModelRegistry.cs @@ -0,0 +1,272 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Connectors; +using Catalyst.Debugging; +using Catalyst.Domains; +using Catalyst.Layers; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Linq; + +using LayerSet = System.Collections.Generic.HashSet>; +using LayerMap = System.Collections.Generic.Dictionary>>; +using DomainMap = System.Collections.Generic.Dictionary>>>; + +using ConnectorSet = System.Collections.Generic.HashSet, Catalyst.Layers.ILayer>>; +using LowLayerConnectorMap = System.Collections.Generic.Dictionary, Catalyst.Layers.ILayer>>>; +using HighLayerLowLayerMap = System.Collections.Generic.Dictionary, Catalyst.Layers.ILayer>>>>; + +namespace Catalyst { + + /// + /// Provides registration services for implementations of the CatalystUI model in .NET Core applications. + /// + public static class ModelRegistry { + + /// + /// A dictionary that maps domain types to their corresponding layers. + /// + private static readonly DomainMap _layers; + + /// + /// A dictionary that maps two layer types to their corresponding connector. + /// + private static readonly HighLayerLowLayerMap _connectors; + + /// + /// The debug context for the model registry. + /// + private static readonly DebugContext _debug; + + /// + /// A lock used for thread-safety. + /// + private static readonly Lock _lock; + + /// + /// Static constructor for . + /// + static ModelRegistry() { + _layers = []; + _connectors = []; + _debug = CatalystDebug.ForContext("ModelRegistry"); + _lock = new(); + } + + /// + /// Registers a layer instance with the model registry. + /// + /// The layer instance to register. + /// The type of the layer instance being registered. + /// Thrown if is null. + /// Thrown if a layer of the same type is already registered for the same domain. + public static void RegisterLayer(TLayer layer) where TLayer : ILayer { + if (layer == null) throw new ArgumentNullException(nameof(layer), "Layer cannot be null."); + _lock.Enter(); + try { + Type domainType = TLayer.Domain; + Type layerType = typeof(TLayer); + + // Ensure the layer map for the domain exists + if (!_layers.TryGetValue(domainType, out LayerMap? layerMap)) { + layerMap = []; + _layers[domainType] = layerMap; + } + + // Ensure the layer instances set for the layer type exists + if (!layerMap.TryGetValue(layerType, out LayerSet? layerSet)) { + layerSet = []; + layerMap[layerType] = layerSet; + } + + // Register the layer instance + if (!layerSet.Add(layer)) { + throw new InvalidOperationException($"The specified layer of type {layerType.FullName} is already registered for domain {domainType.FullName}."); + } + _debug.Log(LogLevel.Info, $"Registered layer of type {layerType.FullName} for domain {domainType.FullName}."); + } finally { + _lock.Exit(); + } + } + + /// + /// Registers a connector instance with the model registry. + /// + /// The connector instance to register. + /// The type of the connector instance being registered. + /// Thrown if is null. + /// Thrown if a connector of the same type is already registered between the same layers. + public static void RegisterConnector(TConnector connector) where TConnector : IConnector, ILayer> { + if (connector == null) throw new ArgumentNullException(nameof(connector), "Connector cannot be null."); + _lock.Enter(); + try { + Type highLayerType = TConnector.HighLayer; + Type lowLayerType = TConnector.LowLayer; + + // Ensure the low layer map for the high layer exists + if (!_connectors.TryGetValue(highLayerType, out LowLayerConnectorMap? lowLayerMap)) { + lowLayerMap = []; + _connectors[highLayerType] = lowLayerMap; + } + + // Ensure the connector set for the low layer type exists + if (!lowLayerMap.TryGetValue(lowLayerType, out ConnectorSet? connectorSet)) { + connectorSet = []; + lowLayerMap[lowLayerType] = connectorSet; + } + + // Register the connector instance + if (!connectorSet.Add(connector)) { + throw new InvalidOperationException($"The specified connector of type {typeof(TConnector).FullName} is already registered between layers {highLayerType.FullName} and {lowLayerType.FullName}."); + } + _debug.Log(LogLevel.Info, $"Registered connector of type {typeof(TConnector).FullName} between layers {highLayerType.FullName} and {lowLayerType.FullName}."); + } finally { + _lock.Exit(); + } + } + + /// + /// Requests for the first registered layer instance of the specified type. + /// + /// + /// When multiple instances of the same layer type are registered, returns the first instance found. + /// + /// The type of layer being requested. + /// The first registered instance of the specified layer type. + /// Thrown if no layer of the specified type is registered. + public static TLayer RequestLayer() where TLayer : ILayer { + _lock.Enter(); + try { + Type domainType = TLayer.Domain; + Type layerType = typeof(TLayer); + + // First, check for an exact type match and return the first instance found + if (_layers.TryGetValue(domainType, out LayerMap? layerMap)) { + if (layerMap.TryGetValue(layerType, out LayerSet? layerSet) && layerSet.Count > 0) { + return (TLayer) layerSet.First(); + } + } + + // Otherwise, search for assignable types and return the first instance found + foreach (KeyValuePair kvp in _layers) { + if (domainType.IsAssignableFrom(kvp.Key)) { + foreach (KeyValuePair innerKvp in kvp.Value) { + if (layerType.IsAssignableFrom(innerKvp.Key) && innerKvp.Value.Count > 0) { + return (TLayer) innerKvp.Value.First(); + } + } + } + } + + // If no layer is found, throw an exception + throw new InvalidOperationException($"No registered layer of type {layerType.FullName} found for domain {domainType.FullName}."); + } finally { + _lock.Exit(); + } + } + + /// + /// Requests all registered layer instances of the specified type. + /// + /// The type of layer being requested. + /// An enumerable for all registered instances of the specified layer type. + public static IEnumerable RequestLayers() where TLayer : ILayer { + _lock.Enter(); + try { + Type domainType = TLayer.Domain; + Type layerType = typeof(TLayer); + foreach (KeyValuePair kvp in _layers) { + if (domainType.IsAssignableFrom(kvp.Key)) { + foreach (KeyValuePair innerKvp in kvp.Value) { + if (layerType.IsAssignableFrom(innerKvp.Key) && innerKvp.Value.Count > 0) { + foreach (ILayer layer in innerKvp.Value) { + yield return (TLayer) layer; + } + } + } + } + } + } finally { + _lock.Exit(); + } + } + + /// + /// Requests for the first registered connector instance of the specified type. + /// + /// + /// When multiple instances of the same connector type are registered, returns the first instance found. + /// + /// The type of connector being requested. + /// The first registered instance of the specified connector type. + /// Thrown if no connector of the specified type is registered. + public static TConnector RequestConnector() where TConnector : IConnector, ILayer> { + _lock.Enter(); + try { + Type highLayerType = TConnector.HighLayer; + Type lowLayerType = TConnector.LowLayer; + + // First, check for an exact type match and return the first instance found + if (_connectors.TryGetValue(highLayerType, out LowLayerConnectorMap? lowLayerMap)) { + if (lowLayerMap.TryGetValue(lowLayerType, out ConnectorSet? connectorSet) && connectorSet.Count > 0) { + return (TConnector) connectorSet.First(); + } + } + + // Otherwise, search for assignable types and return the first instance found + foreach (KeyValuePair kvp in _connectors) { + if (highLayerType.IsAssignableFrom(kvp.Key)) { + foreach (KeyValuePair innerKvp in kvp.Value) { + if (lowLayerType.IsAssignableFrom(innerKvp.Key) && innerKvp.Value.Count > 0) { + return (TConnector) innerKvp.Value.First(); + } + } + } + } + + // If no connector is found, throw an exception + throw new InvalidOperationException($"No registered connector of type {typeof(TConnector).FullName} found between layers {highLayerType.FullName} and {lowLayerType.FullName}."); + } finally { + _lock.Exit(); + } + } + + /// + /// Requests all registered connector instances of the specified type. + /// + /// The type of connector being requested. + /// An enumerable for all registered instances of the specified connector type. + public static IEnumerable RequestConnectors() where TConnector : IConnector, ILayer> { + _lock.Enter(); + try { + Type highLayerType = TConnector.HighLayer; + Type lowLayerType = TConnector.LowLayer; + foreach (KeyValuePair kvp in _connectors) { + if (highLayerType.IsAssignableFrom(kvp.Key)) { + foreach (KeyValuePair innerKvp in kvp.Value) { + if (lowLayerType.IsAssignableFrom(innerKvp.Key) && innerKvp.Value.Count > 0) { + foreach (IConnector, ILayer> connector in innerKvp.Value) { + yield return (TConnector) connector; + } + } + } + } + } + } finally { + _lock.Exit(); + } + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Core/Native/INativeApi.cs b/CatalystUI/Core/CatalystUI.Core/Native/INativeApi.cs new file mode 100644 index 0000000..ea5cdb8 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/Native/INativeApi.cs @@ -0,0 +1,37 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System; + +namespace Catalyst.Native { + + /// + /// Represents a wrapper for an API which provides native access using the Singleton pattern. + /// + /// A reference to the type of the API instance which is generated. + /// A reference to the type of the wrapped API instance, if applicable. + public interface INativeApi : IDisposable where TSelf : INativeApi { + + /// + /// Gets the API instance which is wrapped by this native API. + /// + /// The wrapped API instance. + TApi Api { get; } + + /// + /// Requests an instance of the API. + /// + /// The instance of the API. + static abstract TSelf GetInstance(); + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Core/Native/NativeException.cs b/CatalystUI/Core/CatalystUI.Core/Native/NativeException.cs new file mode 100644 index 0000000..25b52f6 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/Native/NativeException.cs @@ -0,0 +1,48 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System; + +namespace Catalyst.Native { + + /// + /// Represents an error that occurs during native operations. + /// + /// + public class NativeException : Exception { + + /// + /// Initializes a new instance of the class. + /// + public NativeException() : base() { + // ... + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + public NativeException(string message) : base(message) { + // ... + } + + /// + /// Initializes a new instance of the class + /// with a specified error message and a reference to the inner exception + /// that is the cause of this exception. + /// + public NativeException(string message, Exception innerException) : base(message, innerException) { + // ... + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Tooling/CatalystUI.Profiling/CatalystUI.Profiling.csproj b/CatalystUI/Tooling/CatalystUI.Profiling/CatalystUI.Profiling.csproj index 5da9d12..6076f87 100644 --- a/CatalystUI/Tooling/CatalystUI.Profiling/CatalystUI.Profiling.csproj +++ b/CatalystUI/Tooling/CatalystUI.Profiling/CatalystUI.Profiling.csproj @@ -33,6 +33,7 @@ + diff --git a/CatalystUI/Tooling/CatalystUI.Profiling/Properties/catdebug.template.ini b/CatalystUI/Tooling/CatalystUI.Profiling/Properties/catdebug.template.ini index 075aa9c..8642783 100644 --- a/CatalystUI/Tooling/CatalystUI.Profiling/Properties/catdebug.template.ini +++ b/CatalystUI/Tooling/CatalystUI.Profiling/Properties/catdebug.template.ini @@ -9,4 +9,5 @@ ShowStackTrace=false [EnabledScopes] Debugging=info Application=info +ModelRegistry=info Profiling=info \ No newline at end of file From 8e4ae1d1a283a9c8c438e62c77a040a20e22556e Mon Sep 17 00:00:00 2001 From: FireController#1847 Date: Sat, 25 Oct 2025 21:50:21 -0600 Subject: [PATCH 15/20] Add Core/CatalystUI.Debug * Imported the debug project from previous project iterations. * Minor changes were made across the board to bring consistency with the rest of the changes made. * Bumped Catalyst.Debug to 1.0.0-beta.2 * Un-commented the Catalyst.Debug dependency in the profiling project. * --- .scripts/Setup.ps1 | 3 +- CatalystUI/CatalystUI.sln | 7 + .../CatalystUI.Debug/CatalystSerilogDebug.cs | 165 ++++++++++++++++++ .../CatalystUI.Debug/CatalystUI.Debug.csproj | 29 +++ .../CatalystAppBuilderExtensions.cs | 51 ++++++ .../CatalystUI.Debug/SerilogDebugContext.cs | 154 ++++++++++++++++ .../CatalystUI.Profiling.csproj | 2 +- 7 files changed, 409 insertions(+), 2 deletions(-) create mode 100644 CatalystUI/Core/CatalystUI.Debug/CatalystSerilogDebug.cs create mode 100644 CatalystUI/Core/CatalystUI.Debug/CatalystUI.Debug.csproj create mode 100644 CatalystUI/Core/CatalystUI.Debug/Extensions/CatalystAppBuilderExtensions.cs create mode 100644 CatalystUI/Core/CatalystUI.Debug/SerilogDebugContext.cs diff --git a/.scripts/Setup.ps1 b/.scripts/Setup.ps1 index 4bfa893..738efba 100644 --- a/.scripts/Setup.ps1 +++ b/.scripts/Setup.ps1 @@ -48,7 +48,8 @@ $projectsList = @( @{ Folder = "Core"; Name = "CatalystUI.Threading" }, @{ Folder = "Tooling"; Name = "CatalystUI.Analyzers" }, @{ Folder = "Tooling"; Name = "CatalystUI.CodeFix" }, - @{ Folder = "Core"; Name = "CatalystUI.Core" } + @{ Folder = "Core"; Name = "CatalystUI.Core" }, + @{ Folder = "Core"; Name = "CatalystUI.Debug" } ) PromptIgnore = $true } diff --git a/CatalystUI/CatalystUI.sln b/CatalystUI/CatalystUI.sln index 956b7d8..fccd75b 100644 --- a/CatalystUI/CatalystUI.sln +++ b/CatalystUI/CatalystUI.sln @@ -18,6 +18,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CatalystUI.Attributes", "Co EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CatalystUI.CodeFix", "Tooling\CatalystUI.CodeFix\CatalystUI.CodeFix.csproj", "{E5319DB6-E93C-4A7D-9B3B-F219206BBC54}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CatalystUI.Debug", "Core\CatalystUI.Debug\CatalystUI.Debug.csproj", "{39CD2850-CB5B-4F3C-81AB-9430506F3BD7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -52,6 +54,10 @@ Global {E5319DB6-E93C-4A7D-9B3B-F219206BBC54}.Debug|Any CPU.Build.0 = Debug|Any CPU {E5319DB6-E93C-4A7D-9B3B-F219206BBC54}.Release|Any CPU.ActiveCfg = Release|Any CPU {E5319DB6-E93C-4A7D-9B3B-F219206BBC54}.Release|Any CPU.Build.0 = Release|Any CPU + {39CD2850-CB5B-4F3C-81AB-9430506F3BD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {39CD2850-CB5B-4F3C-81AB-9430506F3BD7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {39CD2850-CB5B-4F3C-81AB-9430506F3BD7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {39CD2850-CB5B-4F3C-81AB-9430506F3BD7}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {68F496AC-9438-40F1-9DF8-97363033D661} = {7EC51871-49A8-4991-BDAF-F43A4E9B8C9D} @@ -61,5 +67,6 @@ Global {A3936CB7-DC31-414B-9E40-CB9436391068} = {5D38F696-8C11-4C9A-B50E-2C33AA7FAA6C} {44E8E3D2-FE47-49EA-A397-EB680E33AA2E} = {7EC51871-49A8-4991-BDAF-F43A4E9B8C9D} {E5319DB6-E93C-4A7D-9B3B-F219206BBC54} = {5D38F696-8C11-4C9A-B50E-2C33AA7FAA6C} + {39CD2850-CB5B-4F3C-81AB-9430506F3BD7} = {7EC51871-49A8-4991-BDAF-F43A4E9B8C9D} EndGlobalSection EndGlobal diff --git a/CatalystUI/Core/CatalystUI.Debug/CatalystSerilogDebug.cs b/CatalystUI/Core/CatalystUI.Debug/CatalystSerilogDebug.cs new file mode 100644 index 0000000..3dc2b16 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Debug/CatalystSerilogDebug.cs @@ -0,0 +1,165 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Serilog; +using Serilog.Events; +using Serilog.Templates; +using System; +using System.Diagnostics; +using System.IO; + +namespace Catalyst.Debugging { + + /// + /// Serilog utilities for debugging within the CatalystUI.Debug framework. + /// + public static class CatalystSerilogDebug { + + /// + /// Gets the current logger instance for debugging purposes. + /// + private static ILogger? _logger; + + /// + /// Static constructor for . + /// + static CatalystSerilogDebug() { + // Configure the logger + ConfigureLogger(); + } + + /// + /// Configures the global logger for debugging. + /// + private static void ConfigureLogger() { + LoggerConfiguration config = new(); + + // Apply requested minimum level to the logger configuration + switch (CatalystDebug.DebugOptions.MinimumLogLevel) { + case LogLevel.Critical: + config.MinimumLevel.Fatal(); + break; + case LogLevel.Error: + config.MinimumLevel.Error(); + break; + case LogLevel.Warning: + config.MinimumLevel.Warning(); + break; + case LogLevel.Info: + config.MinimumLevel.Information(); + break; + case LogLevel.Debug: + config.MinimumLevel.Debug(); + break; + case LogLevel.Verbose: + config.MinimumLevel.Verbose(); + break; + case LogLevel.None: + default: + config.MinimumLevel.Debug(); + break; + } + + // Add an async sink + config.WriteTo.Async(writeTo => { + // Create the logging template + string levelmap = @" + if @l = 'Verbose' then 'VERBOSE' + else if @l = 'Debug' then 'DEBUG' + else if @l = 'Information' then 'INFO' + else if @l = 'Warning' then 'WARN' + else if @l = 'Error' then 'ERROR' + else if @l = 'Fatal' then 'CRITICAL' + else 'UNKNOWN' + "; + string thread = $@" + if ThreadName is not null then ThreadName + else if ThreadId = {Environment.CurrentManagedThreadId} then 'MainThread' + else Concat('Thread ', ToString(ThreadId)) + "; + string threadTemplate = CatalystDebug.DebugOptions.ShowsThread ? "<{" + thread + "}> " : string.Empty; + string template = threadTemplate + "[{SourceContext}] [{@t:HH:mm:ss:fff}] [{" + levelmap + "}] {@m}{if @x is not null then '" + Environment.NewLine + "' + @x else ''}"; + ExpressionTemplate formatter = new(template + Environment.NewLine); + + // Add a debug sink + writeTo.Debug(formatter); + + // Add a file sink + try { + if (File.Exists(CatalystDebug.OUTPUT_FILE_NAME)) + File.Delete(CatalystDebug.OUTPUT_FILE_NAME); + } catch { + // eh + } + writeTo.File( + path: CatalystDebug.OUTPUT_FILE_NAME, + retainedFileCountLimit: 1, + formatter: formatter + ); + }); + + // Construct the logger + _logger = config + .Enrich.WithThreadId() + .Enrich.WithThreadName() + .CreateLogger() + .ForContext("SourceContext", ""); + Log.Logger = _logger; // Set the global logger to the configured logger + + // Log initialization + Trace.WriteLine($"ℹ️ [Catalyst.Debugging] Catalyst debugging initialized with minimum log level: {CatalystDebug.DebugOptions.MinimumLogLevel}"); + } + + /// + /// Converts a Serilog to a . + /// + /// The Serilog log event level to convert. + /// A corresponding to the provided Serilog log event level. + public static LogLevel FromLogEventLevel(LogEventLevel level) { + return level switch { + LogEventLevel.Verbose => LogLevel.Verbose, + LogEventLevel.Debug => LogLevel.Debug, + LogEventLevel.Information => LogLevel.Info, + LogEventLevel.Warning => LogLevel.Warning, + LogEventLevel.Error => LogLevel.Error, + LogEventLevel.Fatal => LogLevel.Critical, + _ => LogLevel.None + }; + } + + /// + /// Converts a to a Serilog . + /// + /// The log level to convert. + /// A Serilog corresponding to the provided log level. + public static LogEventLevel ToLogEventLevel(LogLevel logLevel) { + return logLevel switch { + LogLevel.Verbose => LogEventLevel.Verbose, + LogLevel.Debug => LogEventLevel.Debug, + LogLevel.Info => LogEventLevel.Information, + LogLevel.Warning => LogEventLevel.Warning, + LogLevel.Error => LogEventLevel.Error, + LogLevel.Critical => LogEventLevel.Fatal, + _ => LogEventLevel.Debug // Default to Debug if None + }; + } + + /// + /// Requests a logger for the specified scope. + /// + internal static (ILogger, LogLevel)? RequestLogger(string scope) { + if (_logger == null) return null; // how? + return (_logger.ForContext("SourceContext", scope), CatalystDebug.DetermineScopeLevel(scope)); + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Debug/CatalystUI.Debug.csproj b/CatalystUI/Core/CatalystUI.Debug/CatalystUI.Debug.csproj new file mode 100644 index 0000000..f83bfc7 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Debug/CatalystUI.Debug.csproj @@ -0,0 +1,29 @@ + + + + + + Catalyst.Debugging + Catalyst.Debug + + + CatalystUI Debugging + 1.0.0 + beta.2 + FireController#1847 + Debugging API provided by the CatalystUI library. + CatalystUI,debug,debugging + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Debug/Extensions/CatalystAppBuilderExtensions.cs b/CatalystUI/Core/CatalystUI.Debug/Extensions/CatalystAppBuilderExtensions.cs new file mode 100644 index 0000000..b9c28fd --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Debug/Extensions/CatalystAppBuilderExtensions.cs @@ -0,0 +1,51 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Debugging; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Builders.Extensions { + + /// + /// Builder extensions for the . + /// + public static class CatalystAppBuilderExtensions { + + /// + /// Instructs the instance to use the Catalyst.Debug + /// library for debugging operations. + /// + /// + /// + /// This must be the first method called on the + /// instance, as it sets up the debug context for the entire application + /// and any subsequent calls to the methods, + /// including in various static classes and methods. + /// + /// + /// 99.99% of the time, you will want to wrap this method in #if DEBUG + /// preprocessor directives to ensure that it is only included in debug builds. + /// The same is true for the inclusion of the Catalyst.Debug library itself. + /// Failure to do so will result in a large amount of bloat added to the + /// executable built for release, as well as a significant performance + /// impact due to the overhead of the debug logging system. + /// + /// + /// The instance. + /// The modified instance. + public static CatalystAppBuilder UseCatalystDebug(this CatalystAppBuilder builder) { + CatalystDebug.InjectDebugContext(scope => new SerilogDebugContext(scope)); + return builder; + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Debug/SerilogDebugContext.cs b/CatalystUI/Core/CatalystUI.Debug/SerilogDebugContext.cs new file mode 100644 index 0000000..fd3eb67 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Debug/SerilogDebugContext.cs @@ -0,0 +1,154 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Serilog; +using System; +using System.Diagnostics; +using System.IO; +using System.Text; + +namespace Catalyst.Debugging { + + /// + /// A debug context for Serilog integration within the CatalystUI framework. + /// + public sealed class SerilogDebugContext : DebugContext { + + /// + /// The logger instance for this debug context. + /// + public ILogger? Logger { get; } + + /// + public SerilogDebugContext(string scope) : base(scope) { + (ILogger Logger, LogLevel LogLevel)? result = CatalystSerilogDebug.RequestLogger(scope); + if (result == null) { + Logger = null; + } else { + Logger = result.Value.Logger; + Level = result.Value.LogLevel; + } + } + + /// + public override void Log(LogLevel level, string message, string? prefix = null, params object[] args) { + if (Level < level) return; + if (Logger == null) return; + StringBuilder sb = new(); + string? stackTrace = null; + try { + bool needsStackTrace = CatalystDebug.DebugOptions.ShowsFileName || CatalystDebug.DebugOptions.ShowsMethodName || CatalystDebug.DebugOptions.ShowsLineNumber || CatalystDebug.DebugOptions.ShowsStackTrace; + if (needsStackTrace) { + StackTrace trace = new(fNeedFileInfo: true, skipFrames: 2); + if (CatalystDebug.DebugOptions.ShowsStackTrace) stackTrace = trace.ToString(); + StackFrame? frame = trace.GetFrame(0); + DiagnosticMethodInfo? method = frame != null ? DiagnosticMethodInfo.Create(frame) : null; + if (frame != null && method != null) { + bool needsContinuation = false; + bool needsComma = false; + if (CatalystDebug.DebugOptions.ShowsFileName) { + string? file = frame.GetFileName(); + string? typeName = method.Name; + string? assemblyName = method.DeclaringAssemblyName; + sb.Append('<'); + if (!string.IsNullOrEmpty(file)) { + sb.Append(Path.GetFileName(file)); + } else if (!string.IsNullOrEmpty(typeName)) { + sb.Append(typeName); + } else if (!string.IsNullOrEmpty(assemblyName)) { + sb.Append(assemblyName); + } else { + sb.Append("Object"); + } + needsContinuation = true; + needsComma = true; + } + if (CatalystDebug.DebugOptions.ShowsMethodName) { + if (needsComma) { + sb.Append('#'); + } else { + sb.Append('<'); + } + sb.Append(method.Name); + needsContinuation = true; + needsComma = true; + } + if (CatalystDebug.DebugOptions.ShowsLineNumber) { + if (needsComma) { + sb.Append('('); + } else { + sb.Append('<'); + } + sb.Append(frame.GetFileLineNumber()); + if (needsComma) sb.Append(')'); + needsContinuation = true; + needsComma = true; + } + if (needsContinuation) { + sb.Append('>').Append(' '); + } + } + } + } catch { + // not supported probably + } + sb.Append(message).Append(' '); + if (args.Length > 0) { + if (args[0] is Exception e) { + stackTrace = e.StackTrace; + if (e.StackTrace == null) { + stackTrace = e.ToString(); + // Remove the first two lines which are the exception message and type + stackTrace = stackTrace[(stackTrace.IndexOf('\n') + 1)..]; + stackTrace = stackTrace[(stackTrace.IndexOf('\n') + 1)..]; + } + } else { + for (int i = 0; i < args.Length; i++) { + sb.Append(args[i]?.ToString()); + if (i < args.Length - 1) { + sb.Append(' '); + } + } + } + } + if (stackTrace != null) { + sb.AppendLine().Append(stackTrace); + } + switch (level) { + case LogLevel.Critical: + Logger.Fatal(sb.ToString()); + break; + case LogLevel.Error: + Logger.Error(sb.ToString()); + break; + case LogLevel.Warning: + Logger.Warning(sb.ToString()); + break; + case LogLevel.Info: + Logger.Information(sb.ToString()); + break; + case LogLevel.Debug: + Logger.Debug(sb.ToString()); + break; + case LogLevel.Verbose: + Logger.Verbose(sb.ToString()); + break; + case LogLevel.None: + break; // No logging for None level + default: + Logger.Debug(sb.ToString()); // Fall back to Debug for unknown levels + break; + } + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Tooling/CatalystUI.Profiling/CatalystUI.Profiling.csproj b/CatalystUI/Tooling/CatalystUI.Profiling/CatalystUI.Profiling.csproj index 6076f87..73c9f9b 100644 --- a/CatalystUI/Tooling/CatalystUI.Profiling/CatalystUI.Profiling.csproj +++ b/CatalystUI/Tooling/CatalystUI.Profiling/CatalystUI.Profiling.csproj @@ -10,7 +10,7 @@ - + From 0b2ba2fa6cd3d80ca733639d8136678c66093b8c Mon Sep 17 00:00:00 2001 From: FireController#1847 Date: Sat, 25 Oct 2025 21:56:37 -0600 Subject: [PATCH 16/20] Add Core/CatalystUI.Supplementary * Imported the supplementary project from previous project iterations. --- .scripts/Setup.ps1 | 3 +- CatalystUI/CatalystUI.sln | 7 + .../CatalystUI.Supplementary.csproj | 23 +++ .../DataConnector/BindingContract.cs | 107 ++++++++++++ .../DataConnector/FileDataConnectorBase.cs | 157 ++++++++++++++++++ .../DataConnector/IFileDataConnector.cs | 75 +++++++++ .../Layers/DataLayer/IFileInfoDataLayer.cs | 28 ++++ .../Layers/SystemLayer/ILinuxSystemLayer.cs | 27 +++ .../Layers/SystemLayer/IMacSystemLayer.cs | 27 +++ .../Layers/SystemLayer/IWindowsSystemLayer.cs | 27 +++ .../Utilities/SystemDetector.cs | 60 +++++++ .../Properties/catdeps.template.props | 1 + 12 files changed, 541 insertions(+), 1 deletion(-) create mode 100644 CatalystUI/Core/CatalystUI.Supplementary/CatalystUI.Supplementary.csproj create mode 100644 CatalystUI/Core/CatalystUI.Supplementary/Connectors/DataConnector/BindingContract.cs create mode 100644 CatalystUI/Core/CatalystUI.Supplementary/Connectors/DataConnector/FileDataConnectorBase.cs create mode 100644 CatalystUI/Core/CatalystUI.Supplementary/Connectors/DataConnector/IFileDataConnector.cs create mode 100644 CatalystUI/Core/CatalystUI.Supplementary/Layers/DataLayer/IFileInfoDataLayer.cs create mode 100644 CatalystUI/Core/CatalystUI.Supplementary/Layers/SystemLayer/ILinuxSystemLayer.cs create mode 100644 CatalystUI/Core/CatalystUI.Supplementary/Layers/SystemLayer/IMacSystemLayer.cs create mode 100644 CatalystUI/Core/CatalystUI.Supplementary/Layers/SystemLayer/IWindowsSystemLayer.cs create mode 100644 CatalystUI/Core/CatalystUI.Supplementary/Utilities/SystemDetector.cs diff --git a/.scripts/Setup.ps1 b/.scripts/Setup.ps1 index 738efba..69605df 100644 --- a/.scripts/Setup.ps1 +++ b/.scripts/Setup.ps1 @@ -49,7 +49,8 @@ $projectsList = @( @{ Folder = "Tooling"; Name = "CatalystUI.Analyzers" }, @{ Folder = "Tooling"; Name = "CatalystUI.CodeFix" }, @{ Folder = "Core"; Name = "CatalystUI.Core" }, - @{ Folder = "Core"; Name = "CatalystUI.Debug" } + @{ Folder = "Core"; Name = "CatalystUI.Debug" }, + @{ Folder = "Core"; Name = "CatalystUI.Supplementary" } ) PromptIgnore = $true } diff --git a/CatalystUI/CatalystUI.sln b/CatalystUI/CatalystUI.sln index fccd75b..ed2ece7 100644 --- a/CatalystUI/CatalystUI.sln +++ b/CatalystUI/CatalystUI.sln @@ -20,6 +20,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CatalystUI.CodeFix", "Tooli EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CatalystUI.Debug", "Core\CatalystUI.Debug\CatalystUI.Debug.csproj", "{39CD2850-CB5B-4F3C-81AB-9430506F3BD7}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CatalystUI.Supplementary", "Core\CatalystUI.Supplementary\CatalystUI.Supplementary.csproj", "{33B1D211-9C3A-4AA8-95DE-75D9122CC968}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -58,6 +60,10 @@ Global {39CD2850-CB5B-4F3C-81AB-9430506F3BD7}.Debug|Any CPU.Build.0 = Debug|Any CPU {39CD2850-CB5B-4F3C-81AB-9430506F3BD7}.Release|Any CPU.ActiveCfg = Release|Any CPU {39CD2850-CB5B-4F3C-81AB-9430506F3BD7}.Release|Any CPU.Build.0 = Release|Any CPU + {33B1D211-9C3A-4AA8-95DE-75D9122CC968}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {33B1D211-9C3A-4AA8-95DE-75D9122CC968}.Debug|Any CPU.Build.0 = Debug|Any CPU + {33B1D211-9C3A-4AA8-95DE-75D9122CC968}.Release|Any CPU.ActiveCfg = Release|Any CPU + {33B1D211-9C3A-4AA8-95DE-75D9122CC968}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {68F496AC-9438-40F1-9DF8-97363033D661} = {7EC51871-49A8-4991-BDAF-F43A4E9B8C9D} @@ -68,5 +74,6 @@ Global {44E8E3D2-FE47-49EA-A397-EB680E33AA2E} = {7EC51871-49A8-4991-BDAF-F43A4E9B8C9D} {E5319DB6-E93C-4A7D-9B3B-F219206BBC54} = {5D38F696-8C11-4C9A-B50E-2C33AA7FAA6C} {39CD2850-CB5B-4F3C-81AB-9430506F3BD7} = {7EC51871-49A8-4991-BDAF-F43A4E9B8C9D} + {33B1D211-9C3A-4AA8-95DE-75D9122CC968} = {7EC51871-49A8-4991-BDAF-F43A4E9B8C9D} EndGlobalSection EndGlobal diff --git a/CatalystUI/Core/CatalystUI.Supplementary/CatalystUI.Supplementary.csproj b/CatalystUI/Core/CatalystUI.Supplementary/CatalystUI.Supplementary.csproj new file mode 100644 index 0000000..061d8fa --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/CatalystUI.Supplementary.csproj @@ -0,0 +1,23 @@ + + + + + + Catalyst.Supplementary + Catalyst.Supplementary + + + CatalystUI Supplementary + 1.0.0 + beta.2 + FireController#1847 + Supplementary API provided by the CatalystUI library. + CatalystUI,supplementary,addons,additional,extra + + + + + + + + \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Connectors/DataConnector/BindingContract.cs b/CatalystUI/Core/CatalystUI.Supplementary/Connectors/DataConnector/BindingContract.cs new file mode 100644 index 0000000..00672da --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Connectors/DataConnector/BindingContract.cs @@ -0,0 +1,107 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + + + +using System.Threading; +// ReSharper disable once CheckNamespace +using System; + +namespace Catalyst.Supplementary { + + /// + /// Represents a contract for binding data to a specific instance or context. + /// + /// + /// Should be disposed of when no longer needed to allow proper resource cleanup. + /// + // ReSharper disable once ClassNeverInstantiated.Global + public sealed class BindingContract : IDisposable { + + /// + /// Fired when managed resources should be disposed of. + /// + public event Action? DisposeManaged; + + /// + /// Fired when unmanaged resources should be disposed of. + /// + public event Action? DisposeUnmanaged; + + /// + /// The unique identifier for the instance to which the data is bound. + /// + public Guid InstanceId { get; } + + /// + /// A flag indicating whether the object has been disposed of. + /// + private bool _disposed; + + /// + /// A lock used to ensure thread-safe access to the object. + /// + private readonly Lock _lock; + + /// + /// Constructs a new . + /// + public BindingContract() { + // Fields + _disposed = false; + _lock = new(); + + // Properties + InstanceId = Guid.NewGuid(); + } + + /// + /// Disposes of the . + /// + ~BindingContract() { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: false); + } + + /// + /// Disposes of the . + /// + public void Dispose() { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + /// if disposal is being performed by the garbage collector, otherwise + /// + private void Dispose(bool disposing) { + _lock.Enter(); + try { + if (_disposed) return; + + // Dispose managed state (managed objects) + if (disposing) { + DisposeManaged?.Invoke(this); + } + + // Dispose unmanaged state (unmanaged objects) + DisposeUnmanaged?.Invoke(this); + + // Indicate disposal completion + _disposed = true; + } finally { + _lock.Exit(); + } + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Connectors/DataConnector/FileDataConnectorBase.cs b/CatalystUI/Core/CatalystUI.Supplementary/Connectors/DataConnector/FileDataConnectorBase.cs new file mode 100644 index 0000000..f12fe37 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Connectors/DataConnector/FileDataConnectorBase.cs @@ -0,0 +1,157 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Domains; +using Catalyst.Layers; +using System; +using System.Diagnostics.CodeAnalysis; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Supplementary { + + /// + /// A base implementation of . + /// + /// + public abstract class FileDataConnectorBase : IFileDataConnector where TLayerLow : ISemanticsLayer { + + /// + /// A concurrent dictionary mapping binding contracts to their associated file information. + /// + protected readonly ConcurrentDictionary _boundContracts; + + /// + /// A concurrent dictionary mapping file information to their associated file streams. + /// + protected readonly ConcurrentDictionary _boundStreams; + + /// + /// The file mode to use when opening files. + /// + protected readonly FileMode _fileMode; + + /// + /// The file access level to use when opening files. + /// + protected readonly FileAccess _fileAccess; + + /// + /// The file sharing mode to use when opening files. + /// + protected readonly FileShare _fileShare; + + /// + /// A flag indicating whether to enforce exclusive access to files. + /// + protected readonly bool _enforceExclusivity; + + /// + /// Constructs a new . + /// + /// The file mode to use when opening files. + /// The file access level to use when opening files. + /// The file sharing mode to use when opening files. + /// Whether to enforce exclusive access to files. + protected FileDataConnectorBase(FileMode fileMode = FileMode.OpenOrCreate, FileAccess fileAccess = FileAccess.ReadWrite, FileShare fileShare = FileShare.Read, bool enforceExclusivity = false) { + // Fields + _boundContracts = []; + _boundStreams = []; + _fileMode = fileMode; + _fileAccess = fileAccess; + _fileShare = fileShare; + _enforceExclusivity = enforceExclusivity; + } + + /// + public abstract bool TryRead(FileInfo fileInfo, [NotNullWhen(true)] out TSemantic? semantic); + + /// + public bool TryBind(FileInfo fileInfo, [NotNullWhen(true)] out BindingContract? contract) { + // If exclusivity is enforced, check if the file is already bound + if (_enforceExclusivity && _boundStreams.ContainsKey(fileInfo)) { + throw new InvalidOperationException($"File '{fileInfo.FullName}' is already bound to this handler."); + } + + // Create the file stream and the bound file info + FileStream fileStream = fileInfo.Open(_fileMode, _fileAccess, _fileShare); + if (!_boundStreams.TryAdd(fileInfo, fileStream)) { + contract = null; + return false; + } + + // Create the binding contract + contract = new(); + contract.DisposeUnmanaged += OnBindingDisposed; + if (!_boundContracts.TryAdd(contract, fileInfo)) { + // If the binding contract could not be added, clean up the file stream + _boundStreams.TryRemove(fileInfo, out _); + fileStream.Dispose(); + contract = null; // Clear the binding + return false; + } + + // Fire the file bound event + OnFileBound(fileInfo, fileStream); + return true; // success + } + + /// + public abstract bool TryWrite(FileInfo fileInfo, TSemantic semantic); + + /// + /// Fired immediately after a file is bound to the handler. + /// + /// + /// Can be overridden to handle additional logic when a file is bound. + /// + /// The file information that was bound. + /// The file stream that was opened for the file. + protected virtual void OnFileBound(FileInfo fileInfo, FileStream fileStream) { + // ... + } + + /// + /// Fired immediately before a file stream is closed from the handler. + /// + /// + /// Can be overridden to handle additional logic when a file is unbound. + /// + /// The file information that was unbound. + /// The file stream that was closed. + protected virtual void OnFileUnbound(FileInfo fileInfo, FileStream fileStream) { + // ... + } + + /// + /// Fired when a binding is disposed. + /// + /// The binding contract that was disposed. + protected void OnBindingDisposed(BindingContract binding) { + binding.DisposeUnmanaged -= OnBindingDisposed; + + // Find the bound file info associated with the binding + if (_boundContracts.TryRemove(binding, out FileInfo? fileInfo)) { + if (_boundStreams.TryGetValue(fileInfo, out FileStream? fileStream)) { + OnFileUnbound(fileInfo, fileStream); + _boundStreams.Remove(fileInfo, out _); + fileStream.Dispose(); + } + } else { + throw new InvalidOperationException("Failed to remove binding contract from the handler."); + } + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Connectors/DataConnector/IFileDataConnector.cs b/CatalystUI/Core/CatalystUI.Supplementary/Connectors/DataConnector/IFileDataConnector.cs new file mode 100644 index 0000000..b2e2c42 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Connectors/DataConnector/IFileDataConnector.cs @@ -0,0 +1,75 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Layers; +using Catalyst.Connectors; +using Catalyst.Domains; +using System; +using System.Diagnostics.CodeAnalysis; +using System.IO; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Supplementary { + +#pragma warning disable CS1712 // Type parameter has no matching typeparam tag in the XML comment (but other type parameters do) + /// + /// Represents a subset of the data connector specifically designed for handling file data. + /// + /// The semantic type will represent the file data. + /// + public interface IFileDataConnector : IDataConnector> where TLayerLow : ISemanticsLayer { +#pragma warning restore CS1712 // Type parameter has no matching typeparam tag in the XML comment (but other type parameters do) + + /// + /// Attempts to read and parse the file information into the specified semantic type. + /// + /// The file information to be read. + /// A new instance of if the read operation is successful; otherwise, . + /// if the read operation is successful; otherwise, . + /// Thrown if is . + bool TryRead(FileInfo fileInfo, [NotNullWhen(true)] out TSemantic? semantic); + + /// + /// Attempts to bind the file information to the connector. + /// + /// + /// + /// Binding to a file allows the data connector to have active control over the file's data, + /// rather than performing stateless reads and writes. Typically, a data connector will then + /// use an existing to perform reads and writes, which can be + /// significantly faster than stateless operations, which would otherwise require re-reading + /// or completely overwriting the file for every operation. By binding to a file, the connector + /// can potentially determine a difference between the file's current semantic and a provided + /// semantic, significantly improving performance for large files or frequent updates. + /// + /// + /// The outputted is used to manage the lifecycle of the binding, + /// and should be disposed of when no longer needed, allowing the data connector to safely + /// clean up resources and release the file for other operations. + /// + /// + /// The file information to be bound. + /// A new if the bind operation is successful; otherwise, . + /// if the bind operation is successful; otherwise, . + bool TryBind(FileInfo fileInfo, [NotNullWhen(true)] out BindingContract? contract); + + /// + /// Attempts to write the specified semantic data back to the file. + /// + /// The file information where the data will be written. + /// The semantic data to be written. + /// if the write operation is successful; otherwise, . + /// Thrown if or are . + bool TryWrite(FileInfo fileInfo, TSemantic semantic); + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Layers/DataLayer/IFileInfoDataLayer.cs b/CatalystUI/Core/CatalystUI.Supplementary/Layers/DataLayer/IFileInfoDataLayer.cs new file mode 100644 index 0000000..0cdca4e --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Layers/DataLayer/IFileInfoDataLayer.cs @@ -0,0 +1,28 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Domains; +using Catalyst.Layers; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Supplementary { + + /// + /// Represents a subset of the data layer specifically for handling file information. + /// + /// + public interface IFileInfoDataLayer : IDataLayer where TDomain : IDomain { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Layers/SystemLayer/ILinuxSystemLayer.cs b/CatalystUI/Core/CatalystUI.Supplementary/Layers/SystemLayer/ILinuxSystemLayer.cs new file mode 100644 index 0000000..dada472 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Layers/SystemLayer/ILinuxSystemLayer.cs @@ -0,0 +1,27 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Layers; +using Catalyst.Domains; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Supplementary { + + /// + /// Represents a system layer for Linux-based systems. + /// + public interface ILinuxSystemLayer : ISystemLayer { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Layers/SystemLayer/IMacSystemLayer.cs b/CatalystUI/Core/CatalystUI.Supplementary/Layers/SystemLayer/IMacSystemLayer.cs new file mode 100644 index 0000000..d7e7d49 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Layers/SystemLayer/IMacSystemLayer.cs @@ -0,0 +1,27 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Layers; +using Catalyst.Domains; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Supplementary { + + /// + /// Represents a system layer for Apple's macOS-based systems. + /// + public interface IMacSystemLayer : ISystemLayer { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Layers/SystemLayer/IWindowsSystemLayer.cs b/CatalystUI/Core/CatalystUI.Supplementary/Layers/SystemLayer/IWindowsSystemLayer.cs new file mode 100644 index 0000000..1fce9b9 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Layers/SystemLayer/IWindowsSystemLayer.cs @@ -0,0 +1,27 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Domains; +using Catalyst.Layers; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Supplementary { + + /// + /// Represents a system layer for Microsoft's Windows-based systems. + /// + public interface IWindowsSystemLayer : ISystemLayer { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Utilities/SystemDetector.cs b/CatalystUI/Core/CatalystUI.Supplementary/Utilities/SystemDetector.cs new file mode 100644 index 0000000..a1ae07a --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Utilities/SystemDetector.cs @@ -0,0 +1,60 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Domains; +using Catalyst.Layers; +using System; +using System.Runtime.InteropServices; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Supplementary { + + /// + /// A set of utilities to determine the current operating system and environment. + /// + public static class SystemDetector { + + /// + /// Gets the detected operating system type. + /// + public static Type? DetectedSystemType { get; } + + /// + /// Static constructor for . + /// + static SystemDetector() { + // TODO: Is this sufficient? Or should the detection be more advanced? + // Maybe in the future the system layers could provide system information, + // such as versioning or platform-specific features, and then we could + // discern between them here. + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { + DetectedSystemType = typeof(IWindowsSystemLayer); + } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { + DetectedSystemType = typeof(IMacSystemLayer); + } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { + DetectedSystemType = typeof(ILinuxSystemLayer); + } else { + DetectedSystemType = null; + } + } + + /// + /// Determines if the current system is of the specified type. + /// + /// The type of the system to check. + /// if the current system is of the specified type; otherwise, . + public static bool IsSystem() where TSystem : ISystemLayer { + return DetectedSystemType != null && typeof(TSystem).IsAssignableFrom(DetectedSystemType); + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Tooling/CatalystUI.Profiling/Properties/catdeps.template.props b/CatalystUI/Tooling/CatalystUI.Profiling/Properties/catdeps.template.props index 1d3d73e..eeed8e7 100644 --- a/CatalystUI/Tooling/CatalystUI.Profiling/Properties/catdeps.template.props +++ b/CatalystUI/Tooling/CatalystUI.Profiling/Properties/catdeps.template.props @@ -3,5 +3,6 @@ + \ No newline at end of file From 7683019ded27c7350eba47f3214ce3a8dfc19fd8 Mon Sep 17 00:00:00 2001 From: FireController#1847 Date: Sun, 26 Oct 2025 13:34:55 -0600 Subject: [PATCH 17/20] Add Core/CatalystUI.Mathematics * Imported the mathematics project from previous project iterations. * No additional changes were made. --- .scripts/Setup.ps1 | 1 + CatalystUI/CatalystUI.sln | 7 + .../CatalystUI.Core/CatalystUI.Core.csproj | 1 + .../CatalystUI.Debug/CatalystUI.Debug.csproj | 2 +- .../CatalystUI.Mathematics.csproj | 18 ++ .../CatalystUI.Mathematics/Geometry/Angle.cs | 253 +++++++++++++++ .../Geometry/Quadrant.cs | 45 +++ .../Core/CatalystUI.Mathematics/Range.cs | 154 +++++++++ .../Core/CatalystUI.Mathematics/Vector2.cs | 304 ++++++++++++++++++ .../Core/CatalystUI.Mathematics/Vector3.cs | 291 +++++++++++++++++ .../Core/CatalystUI.Mathematics/Vector4.cs | 272 ++++++++++++++++ 11 files changed, 1347 insertions(+), 1 deletion(-) create mode 100644 CatalystUI/Core/CatalystUI.Mathematics/CatalystUI.Mathematics.csproj create mode 100644 CatalystUI/Core/CatalystUI.Mathematics/Geometry/Angle.cs create mode 100644 CatalystUI/Core/CatalystUI.Mathematics/Geometry/Quadrant.cs create mode 100644 CatalystUI/Core/CatalystUI.Mathematics/Range.cs create mode 100644 CatalystUI/Core/CatalystUI.Mathematics/Vector2.cs create mode 100644 CatalystUI/Core/CatalystUI.Mathematics/Vector3.cs create mode 100644 CatalystUI/Core/CatalystUI.Mathematics/Vector4.cs diff --git a/.scripts/Setup.ps1 b/.scripts/Setup.ps1 index 69605df..8f2602c 100644 --- a/.scripts/Setup.ps1 +++ b/.scripts/Setup.ps1 @@ -45,6 +45,7 @@ $projectsList = @( Projects = @( @{ Folder = "Core"; Name = "CatalystUI.Attributes" }, @{ Folder = "Core"; Name = "CatalystUI.Collections" }, + @{ Folder = "Core"; Name = "CatalystUI.Mathematics" }, @{ Folder = "Core"; Name = "CatalystUI.Threading" }, @{ Folder = "Tooling"; Name = "CatalystUI.Analyzers" }, @{ Folder = "Tooling"; Name = "CatalystUI.CodeFix" }, diff --git a/CatalystUI/CatalystUI.sln b/CatalystUI/CatalystUI.sln index ed2ece7..4ff5395 100644 --- a/CatalystUI/CatalystUI.sln +++ b/CatalystUI/CatalystUI.sln @@ -22,6 +22,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CatalystUI.Debug", "Core\Ca EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CatalystUI.Supplementary", "Core\CatalystUI.Supplementary\CatalystUI.Supplementary.csproj", "{33B1D211-9C3A-4AA8-95DE-75D9122CC968}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CatalystUI.Mathematics", "Core\CatalystUI.Mathematics\CatalystUI.Mathematics.csproj", "{E2B3A13C-9AE6-44D8-8456-58723ABBC343}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -64,6 +66,10 @@ Global {33B1D211-9C3A-4AA8-95DE-75D9122CC968}.Debug|Any CPU.Build.0 = Debug|Any CPU {33B1D211-9C3A-4AA8-95DE-75D9122CC968}.Release|Any CPU.ActiveCfg = Release|Any CPU {33B1D211-9C3A-4AA8-95DE-75D9122CC968}.Release|Any CPU.Build.0 = Release|Any CPU + {E2B3A13C-9AE6-44D8-8456-58723ABBC343}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E2B3A13C-9AE6-44D8-8456-58723ABBC343}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E2B3A13C-9AE6-44D8-8456-58723ABBC343}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E2B3A13C-9AE6-44D8-8456-58723ABBC343}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {68F496AC-9438-40F1-9DF8-97363033D661} = {7EC51871-49A8-4991-BDAF-F43A4E9B8C9D} @@ -75,5 +81,6 @@ Global {E5319DB6-E93C-4A7D-9B3B-F219206BBC54} = {5D38F696-8C11-4C9A-B50E-2C33AA7FAA6C} {39CD2850-CB5B-4F3C-81AB-9430506F3BD7} = {7EC51871-49A8-4991-BDAF-F43A4E9B8C9D} {33B1D211-9C3A-4AA8-95DE-75D9122CC968} = {7EC51871-49A8-4991-BDAF-F43A4E9B8C9D} + {E2B3A13C-9AE6-44D8-8456-58723ABBC343} = {7EC51871-49A8-4991-BDAF-F43A4E9B8C9D} EndGlobalSection EndGlobal diff --git a/CatalystUI/Core/CatalystUI.Core/CatalystUI.Core.csproj b/CatalystUI/Core/CatalystUI.Core/CatalystUI.Core.csproj index 81d5241..f4acb4e 100644 --- a/CatalystUI/Core/CatalystUI.Core/CatalystUI.Core.csproj +++ b/CatalystUI/Core/CatalystUI.Core/CatalystUI.Core.csproj @@ -21,6 +21,7 @@ + diff --git a/CatalystUI/Core/CatalystUI.Debug/CatalystUI.Debug.csproj b/CatalystUI/Core/CatalystUI.Debug/CatalystUI.Debug.csproj index f83bfc7..e4d4cbc 100644 --- a/CatalystUI/Core/CatalystUI.Debug/CatalystUI.Debug.csproj +++ b/CatalystUI/Core/CatalystUI.Debug/CatalystUI.Debug.csproj @@ -10,7 +10,7 @@ CatalystUI Debugging 1.0.0 beta.2 - FireController#1847 + CatalystUI LLC Debugging API provided by the CatalystUI library. CatalystUI,debug,debugging diff --git a/CatalystUI/Core/CatalystUI.Mathematics/CatalystUI.Mathematics.csproj b/CatalystUI/Core/CatalystUI.Mathematics/CatalystUI.Mathematics.csproj new file mode 100644 index 0000000..75a29c1 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Mathematics/CatalystUI.Mathematics.csproj @@ -0,0 +1,18 @@ + + + + + + Catalyst.Mathematics + Catalyst.Mathematics + + + CatalystUI Mathematics + 1.0.0 + beta.2 + CatalystUI LLC + Mathematics API provided by the CatalystUI library. + CatalystUI,math,mathematics + + + \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Mathematics/Geometry/Angle.cs b/CatalystUI/Core/CatalystUI.Mathematics/Geometry/Angle.cs new file mode 100644 index 0000000..0f7d5c1 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Mathematics/Geometry/Angle.cs @@ -0,0 +1,253 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Catalyst.Mathematics.Geometry { + + /// + /// A programmatic representation of an angle. + /// + [StructLayout(LayoutKind.Sequential)] + public readonly record struct Angle { + + /// + /// The underlying angle in radians. + /// + private readonly double _radians; + + /// + /// Gets the angle in degrees. + /// + /// The angle in degrees. + public double Degrees => RadiansToDegrees(_radians); + + /// + /// Gets the angle in radians. + /// + /// The angle in radians. + public double Radians => _radians; + + /// + /// Gets the angle in gradians (also known as gon). + /// + /// The angle is gradians (gon). + public double Gradians => RadiansToGradians(_radians); + + /// + /// Constructs a new . + /// + /// The angle in radians. + private Angle(double radians) { + _radians = radians; + } + + /// + /// Converts an angle from radians to an instance. + /// + /// The angle in radians. + /// An instance representing the angle in radians. + public static Angle FromRadians(double radians) { + return new(radians); + } + + /// + /// Converts an angle from degrees to an instance. + /// + /// The angle in degrees. + /// An instance representing the angle in degrees. + public static Angle FromDegrees(double degrees) { + return new(DegreesToRadians(degrees)); + } + + /// + /// Converts an angle from gradians (gon) to an instance. + /// + /// The angle in gradians (gon). + /// An instance representing the angle in gradians. + public static Angle FromGradians(double gradians) { + return new(GradiansToRadians(gradians)); + } + + /// + /// Normalizes the angle to a value between 0 and 2π radians (0 and 360 degrees). + /// + /// The normalized angle. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Angle Normalize() { + return new((_radians % (2 * Math.PI) + 2 * Math.PI) % (2 * Math.PI)); + } + + /// + /// Gets the quadrant of the angle. + /// + /// The quadrant of the angle. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Quadrant ToQuadrant() { + return (Quadrant) ((int) (Normalize()._radians / (Math.PI / 2)) + 1); + } + + /// + /// Converts an angle from radians to degrees. + /// + /// The angle in radians. + /// The angle in degrees. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double RadiansToDegrees(double radians) { + return radians * (180.0 / Math.PI); + } + + /// + /// Converts an angle from radians to gradians (gon). + /// + /// The angle in radians. + /// The angle in gradians. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double RadiansToGradians(double radians) { + return radians * (200.0 / Math.PI); + } + + /// + /// Converts an angle from degrees to radians. + /// + /// The angle in degrees. + /// The angle in radians. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double DegreesToRadians(double degrees) { + return degrees * (Math.PI / 180.0); + } + + /// + /// Converts an angle from degrees to gradians (gon). + /// + /// The angle in degrees. + /// The angle in gradians. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double DegreesToGradians(double degrees) { + return degrees * (10.0 / 9.0); + } + + /// + /// Converts an angle from gradians (gon) to radians. + /// + /// The angle in gradians. + /// The angle in radians. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double GradiansToRadians(double gradians) { + return gradians * (Math.PI / 200.0); + } + + /// + /// Converts an angle from gradians (gon) to degrees. + /// + /// The angle in gradians. + /// The angle in degrees. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double GradiansToDegrees(double gradians) { + return gradians * (9.0 / 1.0); + } + + /// + /// Returns the value of the operand angle without modification. + /// + /// The angle to return. + /// The same angle instance. + public static Angle operator +(Angle angle) { + return angle; + } + + /// + /// Returns the negated value of the operand angle. + /// + /// The angle to negate. + /// The negated angle instance. + public static Angle operator -(Angle angle) { + return new(-angle._radians); + } + + /// + /// Increments the angle by 1 degree (approximately 0.0174533 radians). + /// + /// The angle to increment. + /// A new angle instance incremented by 1 degree. + public static Angle operator ++(Angle angle) { + return new(angle._radians + DegreesToRadians(1)); + } + + /// + /// Decrements the angle by 1 degree (approximately 0.0174533 radians). + /// + /// The angle to decrement. + /// A new angle instance decremented by 1 degree. + public static Angle operator --(Angle angle) { + return new(angle._radians - DegreesToRadians(1)); + } + + /// + /// Adds two angles together, resulting in a new angle. + /// + /// The first angle to add. + /// The second angle to add. + /// A new angle that is the sum of the two angles. + public static Angle operator +(Angle left, Angle right) { + return new(left._radians + right._radians); + } + + /// + /// Subtracts one angle from another, resulting in a new angle. + /// + /// The angle to subtract from. + /// The angle to subtract. + /// A new angle that is the difference of the two angles. + public static Angle operator -(Angle left, Angle right) { + return new(left._radians - right._radians); + } + + /// + /// Multiplies an angle by a scalar value, resulting in a new angle. + /// + /// The angle to multiply. + /// The scalar value to multiply the angle by. + /// A new angle that is the product of the angle and the scalar. + public static Angle operator *(Angle angle, double scalar) { + return new(angle._radians * scalar); + } + + /// + /// Divides an angle by a scalar value, resulting in a new angle. + /// + /// The angle to divide. + /// The scalar value to divide the angle by. + /// A new angle that is the quotient of the angle and the scalar. + public static Angle operator /(Angle angle, double scalar) { + return new(angle._radians / scalar); + } + + /// + /// Modulo operation on an angle with a scalar value, resulting in a new angle. + /// + /// The angle to apply the modulo operation on. + /// The scalar value to apply the modulo operation with. + /// A new angle that is the result of the modulo operation. + public static Angle operator %(Angle angle, double scalar) { + return new(angle._radians % scalar); + } + + /// + public override string ToString() { + return $"{Degrees:F1}°"; + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Mathematics/Geometry/Quadrant.cs b/CatalystUI/Core/CatalystUI.Mathematics/Geometry/Quadrant.cs new file mode 100644 index 0000000..0d4c7b8 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Mathematics/Geometry/Quadrant.cs @@ -0,0 +1,45 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +namespace Catalyst.Mathematics.Geometry { + + /// + /// A list of quadrants in a Cartesian coordinate system. + /// + public enum Quadrant { + + /// + /// The first quadrant (Q1) or top-right quadrant, where both x and y coordinates are positive. + /// + /// 1 + First = 1, + + /// + /// The second quadrant (Q2) or top-left quadrant, where x is negative and y is positive. + /// + /// 2 + Second = 2, + + /// + /// The third quadrant (Q3) or bottom-left quadrant, where both x and y coordinates are negative. + /// + /// 3 + Third = 3, + + /// + /// The fourth quadrant (Q4) or bottom-right quadrant, where x is positive and y is negative. + /// + /// 4 + Fourth = 4 + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Mathematics/Range.cs b/CatalystUI/Core/CatalystUI.Mathematics/Range.cs new file mode 100644 index 0000000..46a60db --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Mathematics/Range.cs @@ -0,0 +1,154 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Numerics; +using System.Text; + +namespace Catalyst.Mathematics { + + /// + /// Represents a range of numeric values. + /// + /// The numeric type of the range values. + public readonly record struct Range where TNumber : struct, INumber { + + /// + /// The zero range, where both minimum and maximum are zero and inclusive. + /// + public static readonly Range ZERO = new(RangeValue.ZERO_INCLUSIVE, RangeValue.ZERO_INCLUSIVE); + + // Backing Fields + private readonly RangeValue _minimum; + private readonly RangeValue _maximum; + + /// + /// Gets the minimum value of the range. + /// + /// The range's minimum value. + public required RangeValue Minimum { + get => _minimum; + init { + _minimum = value; + if (_maximum != default) Validate(); + } + } + + /// + /// Gets the maximum value of the range. + /// + /// The range's maximum value. + public required RangeValue Maximum { + get => _maximum; + init { + _maximum = value; + if (_minimum != default) Validate(); + } + } + + /// + /// Constructs a new + /// with the specified minimum and maximum values. + /// + /// The minimum value of the range. + /// The maximum value of the range. + [SetsRequiredMembers] + public Range(RangeValue minimum, RangeValue maximum) { + Minimum = minimum; + Maximum = maximum; + Validate(); + } + + /// + /// Determines if the specified number is within the range. + /// + /// The number to check. + /// if the number is within the range; otherwise, . + public bool Within(TNumber number) { + bool aboveMinimum = _minimum.Exclusive ? number > _minimum.Value : number >= _minimum.Value; + bool belowMaximum = _maximum.Exclusive ? number < _maximum.Value : number <= _maximum.Value; + return aboveMinimum && belowMaximum; + } + + /// + /// Validates the range values. + /// + /// Thrown if the maximum value is less than the minimum value. + private void Validate() { + if (_minimum.Value > _maximum.Value) throw new ArgumentOutOfRangeException(nameof(_maximum), "The maximum value must be greater than or equal to the minimum value."); + if (_minimum.Value == _maximum.Value && (_minimum.Exclusive || _maximum.Exclusive)) throw new ArgumentOutOfRangeException(nameof(_maximum), "The maximum value must be greater than the minimum value when either bound is exclusive."); + } + + /// + public override string ToString() { + StringBuilder sb = new(); + sb.Append(Minimum.Exclusive ? '(' : '['); + sb.Append(Minimum.Value); + sb.Append(',').Append(' '); + sb.Append(Maximum.Value); + sb.Append(Maximum.Exclusive ? ')' : ']'); + return sb.ToString(); + } + + } + + /// + /// Represents a number value within a specified range. + /// + /// The numeric type of the range value. + public readonly record struct RangeValue where TNumber : struct, INumber { + + /// + /// A zero value which is inclusive within the range. + /// + public static readonly RangeValue ZERO_INCLUSIVE = new(TNumber.Zero, false); + + /// + /// A zero value which is exclusive outside the range. + /// + public static readonly RangeValue ZERO_EXCLUSIVE = new(TNumber.Zero, true); + + /// + /// Gets the underlying value. + /// + /// The underlying value. + public required TNumber Value { get; init; } + + /// + /// Gets a flag indicating whether the value is exclusive outside the range. + /// + /// if the value is exclusive; otherwise, . + public required bool Exclusive { get; init; } + + /// + /// Constructs a new . + /// + /// The underlying value. + /// A flag indicating whether the value is exclusive outside the range. + [SetsRequiredMembers] + public RangeValue(TNumber value, bool exclusive) { + Value = value; + Exclusive = exclusive; + } + + /// + /// Implicitly converts a range value to its underlying numeric type. + /// + /// The range value to convert. + /// The underlying numeric value. + public static implicit operator TNumber(RangeValue rv) { + return rv.Value; + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Mathematics/Vector2.cs b/CatalystUI/Core/CatalystUI.Mathematics/Vector2.cs new file mode 100644 index 0000000..429fbb4 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Mathematics/Vector2.cs @@ -0,0 +1,304 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Numerics; +using System.Runtime.InteropServices; + +namespace Catalyst.Mathematics { + + /// + /// A vector containing two numeric values. + /// + /// The numeric type of the vector values. + [StructLayout(LayoutKind.Sequential)] + public readonly record struct Vector2 where TNumber : struct, INumber { + + /// + /// The common zero vector (0, 0). + /// + public static readonly Vector2 ZERO = new(TNumber.Zero); + + /// + /// The common unit vector (1, 1). + /// + public static readonly Vector2 UNIT = new(TNumber.One); + + /// + /// Gets the X value of the vector. + /// + /// The vector's X value. + public required TNumber X { get; init; } + + /// + /// Gets the Y value of the vector. + /// + /// The vector's Y value. + public required TNumber Y { get; init; } + + /// + /// Gets the R value of the vector (alias for X). + /// + /// The vector's R value. + public TNumber R => X; + + /// + /// Gets the G value of the vector (alias for Y). + /// + /// The vector's G value. + public TNumber G => Y; + + /// + /// Gets the S value of the vector (alias for X). + /// + /// The vector's S value. + public TNumber S => X; + + /// + /// Gets the T value of the vector (alias for Y). + /// + /// The vector's T value. + public TNumber T => Y; + + /// + /// Constructs a new + /// using the specified X and Y values. + /// + /// The X value of the vector. + /// The Y value of the vector. + [SetsRequiredMembers] + public Vector2(TNumber x, TNumber y) { + X = x; + Y = y; + } + + /// + /// Constructs a new + /// using the specified value for both X and Y. + /// + /// The value for both X and Y of the vector. + [SetsRequiredMembers] + public Vector2(TNumber xy) : this(xy, xy) { + // ... + } + + /// + /// Normalizes the vector by preserving direction + /// and setting its length to 1. + /// + /// + /// When calculating a normalized vector, + /// the value is first converted to a + /// double-precision floating point number, + /// which allows the necessary mathematical + /// operations to be performed. It is then + /// converted back to the original numeric + /// type. + /// + /// A new vector with the same direction and a length of 1. + public Vector2 Normalize() { + TNumber lengthSquared = X * X + Y * Y; + if (lengthSquared == TNumber.Zero) return ZERO; + double lengthInverted = 1.0 / Math.Sqrt(double.CreateChecked(lengthSquared)); + TNumber lengthConverted = TNumber.CreateChecked(lengthInverted); + return new(X * lengthConverted, Y * lengthConverted); + } + + /// + /// Converts a from + /// into a from . + /// + /// The newly created . + public Vector2 ToVector2() { + return new( + float.CreateChecked(X), + float.CreateChecked(Y) + ); + } + + /// + /// Converts a from + /// into a from . + /// + /// The to convert. + /// The newly created . + public static Vector2 FromVector2(Vector2 vector2) { + return new( + TNumber.CreateChecked(vector2.X), + TNumber.CreateChecked(vector2.Y) + ); + } + + /// + /// Converts the vector to a different numeric type. + /// + /// The vector to convert. + /// The numeric type to convert to. + /// The newly created vector with the specified numeric type. + public static Vector2 ConvertTo(Vector2 vector) where TToNumber : struct, INumber { + return new( + TToNumber.CreateChecked(vector.X), + TToNumber.CreateChecked(vector.Y) + ); + } + + /// + /// Linearly interpolates between two vectors. + /// + /// The starting vector. + /// The ending vector. + /// The interpolation position, typically between 0 and 1. + /// The interpolated vector. + public static Vector2 Lerp(Vector2 v1, Vector2 v2, TNumber position) { + return new( + v1.X + (v2.X - v1.X) * position, + v1.Y + (v2.Y - v1.Y) * position + ); + } + + /// + /// Calculates the dot product of two vectors. + /// + /// The first vector. + /// The second vector. + /// The dot product of the two vectors. + public static TNumber Dot(Vector2 v1, Vector2 v2) { + return v1.X * v2.X + v1.Y * v2.Y; + } + + /// + /// Calculates the distance between two vectors. + /// + /// The first vector. + /// The second vector. + /// The distance between the two vectors. + public static TNumber Distance(Vector2 v1, Vector2 v2) { + TNumber deltaX = v2.X - v1.X; + TNumber deltaY = v2.Y - v1.Y; + double distance = Math.Sqrt(double.CreateChecked(deltaX * deltaX + deltaY * deltaY)); + return TNumber.CreateChecked(distance); + } + + /// + /// Compares two vectors to determine if the left vector is less than the right vector. + /// + /// The left vector. + /// The right vector. + /// if the left vector is less than the right vector; otherwise, . + public static bool operator <(Vector2 left, Vector2 right) => left.X < right.X && left.Y < right.Y; + + /// + /// Compares two vectors to determine if the left vector is less than or equal to the right vector. + /// + /// The left vector. + /// The right vector. + /// if the left vector is less than or equal to the right vector; otherwise, . + public static bool operator <=(Vector2 left, Vector2 right) => left.X <= right.X && left.Y <= right.Y; + + /// + /// Compares two vectors to determine if the left vector is greater than the right vector. + /// + /// The left vector. + /// The right vector. + /// if the left vector is greater than the right vector; otherwise, . + public static bool operator >(Vector2 left, Vector2 right) => left.X > right.X && left.Y > right.Y; + + /// + /// Compares two vectors to determine if the left vector is greater than or equal to the right vector. + /// + /// The left vector. + /// The right vector. + /// if the left vector is greater than or equal to the right vector; otherwise, . + public static bool operator >=(Vector2 left, Vector2 right) => left.X >= right.X && left.Y >= right.Y; + + /// + /// Unary plus operator. + /// + /// The vector to return. + /// The vector unchanged. + public static Vector2 operator +(Vector2 vector) => vector; + + /// + /// Unary negation operator. + /// + /// The vector to negate. + /// The vector with both X and Y negated. + public static Vector2 operator -(Vector2 vector) => new(-vector.X, -vector.Y); + + /// + /// Finds the sum of two vectors. + /// + /// The left vector. + /// The right vector. + /// The sum of the two vectors. + public static Vector2 operator +(Vector2 left, Vector2 right) => new(left.X + right.X, left.Y + right.Y); + + /// + /// Finds the difference between two vectors. + /// + /// The left vector. + /// The right vector. + /// The difference of the two vectors. + public static Vector2 operator -(Vector2 left, Vector2 right) => new(left.X - right.X, left.Y - right.Y); + + /// + /// Increments the vector by one. + /// + /// The vector to increment. + /// The vector with both X and Y incremented by one. + public static Vector2 operator ++(Vector2 vector) => new(vector.X + TNumber.One, vector.Y + TNumber.One); + + /// + /// Decrements the vector by one. + /// + /// The vector to decrement. + /// The vector with both X and Y decremented by one. + public static Vector2 operator --(Vector2 vector) => new(vector.X - TNumber.One, vector.Y - TNumber.One); + + /// + /// Multiplies the vector by a scalar value. + /// + /// The vector to multiply. + /// The scalar value to multiply by. + /// The vector scaled by the scalar value. + public static Vector2 operator *(Vector2 vector, TNumber scalar) => new(vector.X * scalar, vector.Y * scalar); + + /// + public static Vector2 operator *(TNumber scalar, Vector2 vector) => new(vector.X * scalar, vector.Y * scalar); + + /// + /// Multiplies two vectors together. + /// + /// The left vector. + /// The right vector. + /// The vector with each value multiplied together. + public static Vector2 operator *(Vector2 left, Vector2 right) => new(left.X * right.X, left.Y * right.Y); + + /// + /// Divides the vector by a scalar value. + /// + /// The vector to divide. + /// The scalar value to divide by. + /// The vector divided by the scalar value. + public static Vector2 operator /(Vector2 vector, TNumber scalar) => new(vector.X / scalar, vector.Y / scalar); + + /// + /// Finds the quotient of two vectors. + /// + /// The left vector. + /// The right vector. + /// The vector with each value divided. + public static Vector2 operator /(Vector2 left, Vector2 right) => new(left.X / right.X, left.Y / right.Y); + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Mathematics/Vector3.cs b/CatalystUI/Core/CatalystUI.Mathematics/Vector3.cs new file mode 100644 index 0000000..147132b --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Mathematics/Vector3.cs @@ -0,0 +1,291 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Numerics; +using System.Runtime.InteropServices; + +namespace Catalyst.Mathematics { + + /// + /// A vector containing three numeric values. + /// + /// The numeric type of the vector values. + [StructLayout(LayoutKind.Sequential)] + public readonly record struct Vector3 where TNumber : struct, INumber { + + /// + /// The common zero vector (0, 0, 0). + /// + public static readonly Vector3 ZERO = new(TNumber.Zero); + + /// + /// The common unit vector (1, 1, 1). + /// + public static readonly Vector3 UNIT = new(TNumber.One); + + /// + /// Gets the X value of the vector. + /// + /// The vector's X value. + public required TNumber X { get; init; } + + /// + /// Gets the Y value of the vector. + /// + /// The vector's Y value. + public required TNumber Y { get; init; } + + /// + /// Gets the Z value of the vector. + /// + /// The vector's Z value. + public required TNumber Z { get; init; } + + /// + /// Gets the R value of the vector (alias for X). + /// + /// The vector's R value. + public TNumber R => X; + + /// + /// Gets the G value of the vector (alias for Y). + /// + /// The vector's G value. + public TNumber G => Y; + + /// + /// Gets the B value of the vector (alias for Z). + /// + /// The vector's B value. + public TNumber B => Z; + + /// + /// Gets the S value of the vector (alias for X). + /// + /// The vector's S value. + public TNumber S => X; + + /// + /// Gets the T value of the vector (alias for Y). + /// + /// The vector's T value. + public TNumber T => Y; + + /// + /// Gets the P value of the vector (alias for Z). + /// + /// The vector's P value. + public TNumber P => Z; + + /// + /// Constructs a new + /// using the specified X, Y, and Z values. + /// + /// The X value of the vector. + /// The Y value of the vector. + /// The Z value of the vector. + [SetsRequiredMembers] + public Vector3(TNumber x, TNumber y, TNumber z) { + X = x; + Y = y; + Z = z; + } + + /// + /// Constructs a new + /// using the specified value for X, Y, and Z. + /// + /// The value for all components of the vector. + [SetsRequiredMembers] + public Vector3(TNumber xyz) : this(xyz, xyz, xyz) { + // ... + } + + /// + /// Normalizes the vector by preserving direction + /// and setting its length to 1. + /// + /// + /// When calculating a normalized vector, + /// the value is first converted to a + /// double-precision floating point number, + /// which allows the necessary mathematical + /// operations to be performed. It is then + /// converted back to the original numeric + /// type. + /// + /// A new vector with the same direction and a length of 1. + public Vector3 Normalize() { + TNumber lengthSquared = X * X + Y * Y + Z * Z; + if (lengthSquared == TNumber.Zero) return ZERO; + double lengthInverted = 1.0 / Math.Sqrt(double.CreateChecked(lengthSquared)); + TNumber lengthConverted = TNumber.CreateChecked(lengthInverted); + return new(X * lengthConverted, Y * lengthConverted, Z * lengthConverted); + } + + /// + /// Converts a from + /// into a from . + /// + /// The newly created . + public Vector3 ToVector3() { + return new( + float.CreateChecked(X), + float.CreateChecked(Y), + float.CreateChecked(Z) + ); + } + + /// + /// Converts a from + /// into a from . + /// + /// The to convert. + /// The newly created . + public static Vector3 FromVector3(Vector3 vector3) { + return new( + TNumber.CreateChecked(vector3.X), + TNumber.CreateChecked(vector3.Y), + TNumber.CreateChecked(vector3.Z) + ); + } + + /// + /// Converts the vector to a different numeric type. + /// + /// The vector to convert. + /// The numeric type to convert to. + /// The newly created vector with the specified numeric type. + public static Vector3 ConvertTo(Vector3 vector) where TToNumber : struct, INumber { + return new( + TToNumber.CreateChecked(vector.X), + TToNumber.CreateChecked(vector.Y), + TToNumber.CreateChecked(vector.Z) + ); + } + + /// + /// Linearly interpolates between two vectors. + /// + /// The starting vector. + /// The ending vector. + /// The interpolation position, typically between 0 and 1. + /// The interpolated vector. + public static Vector3 Lerp(Vector3 v1, Vector3 v2, TNumber position) { + return new( + v1.X + (v2.X - v1.X) * position, + v1.Y + (v2.Y - v1.Y) * position, + v1.Z + (v2.Z - v1.Z) * position + ); + } + + /// + /// Calculates the dot product of two vectors. + /// + /// The first vector. + /// The second vector. + /// The dot product of the two vectors. + public static TNumber Dot(Vector3 v1, Vector3 v2) { + return v1.X * v2.X + v1.Y * v2.Y + v1.Z * v2.Z; + } + + /// + /// Calculates the distance between two vectors. + /// + /// The first vector. + /// The second vector. + /// The distance between the two vectors. + public static TNumber Distance(Vector3 v1, Vector3 v2) { + TNumber deltaX = v2.X - v1.X; + TNumber deltaY = v2.Y - v1.Y; + TNumber deltaZ = v2.Z - v1.Z; + double distance = Math.Sqrt(double.CreateChecked(deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ)); + return TNumber.CreateChecked(distance); + } + + /// + /// Compares two vectors to determine if the left vector is less than the right vector. + /// + public static bool operator <(Vector3 left, Vector3 right) => left.X < right.X && left.Y < right.Y && left.Z < right.Z; + + /// + /// Compares two vectors to determine if the left vector is less than or equal to the right vector. + /// + public static bool operator <=(Vector3 left, Vector3 right) => left.X <= right.X && left.Y <= right.Y && left.Z <= right.Z; + + /// + /// Compares two vectors to determine if the left vector is greater than the right vector. + /// + public static bool operator >(Vector3 left, Vector3 right) => left.X > right.X && left.Y > right.Y && left.Z > right.Z; + + /// + /// Compares two vectors to determine if the left vector is greater than or equal to the right vector. + /// + public static bool operator >=(Vector3 left, Vector3 right) => left.X >= right.X && left.Y >= right.Y && left.Z >= right.Z; + + /// + /// Unary plus operator. + /// + public static Vector3 operator +(Vector3 vector) => vector; + + /// + /// Unary negation operator. + /// + public static Vector3 operator -(Vector3 vector) => new(-vector.X, -vector.Y, -vector.Z); + + /// + /// Finds the sum of two vectors. + /// + public static Vector3 operator +(Vector3 left, Vector3 right) => new(left.X + right.X, left.Y + right.Y, left.Z + right.Z); + + /// + /// Finds the difference between two vectors. + /// + public static Vector3 operator -(Vector3 left, Vector3 right) => new(left.X - right.X, left.Y - right.Y, left.Z - right.Z); + + /// + /// Increments the vector by one. + /// + public static Vector3 operator ++(Vector3 vector) => new(vector.X + TNumber.One, vector.Y + TNumber.One, vector.Z + TNumber.One); + + /// + /// Decrements the vector by one. + /// + public static Vector3 operator --(Vector3 vector) => new(vector.X - TNumber.One, vector.Y - TNumber.One, vector.Z - TNumber.One); + + /// + /// Multiplies the vector by a scalar value. + /// + public static Vector3 operator *(Vector3 vector, TNumber scalar) => new(vector.X * scalar, vector.Y * scalar, vector.Z * scalar); + + /// + public static Vector3 operator *(TNumber scalar, Vector3 vector) => new(vector.X * scalar, vector.Y * scalar, vector.Z * scalar); + + /// + /// Multiplies two vectors together. + /// + public static Vector3 operator *(Vector3 left, Vector3 right) => new(left.X * right.X, left.Y * right.Y, left.Z * right.Z); + + /// + /// Divides the vector by a scalar value. + /// + public static Vector3 operator /(Vector3 vector, TNumber scalar) => new(vector.X / scalar, vector.Y / scalar, vector.Z / scalar); + + /// + /// Finds the quotient of two vectors. + /// + public static Vector3 operator /(Vector3 left, Vector3 right) => new(left.X / right.X, left.Y / right.Y, left.Z / right.Z); + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Mathematics/Vector4.cs b/CatalystUI/Core/CatalystUI.Mathematics/Vector4.cs new file mode 100644 index 0000000..002c16e --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Mathematics/Vector4.cs @@ -0,0 +1,272 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Numerics; +using System.Runtime.InteropServices; + +namespace Catalyst.Mathematics { + + /// + /// A vector containing four numeric values. + /// + /// The numeric type of the vector values. + [StructLayout(LayoutKind.Sequential)] + public readonly record struct Vector4 where TNumber : struct, INumber { + + /// + /// The common zero vector (0, 0, 0, 0). + /// + public static readonly Vector4 ZERO = new(TNumber.Zero); + + /// + /// The common unit vector (1, 1, 1, 1). + /// + public static readonly Vector4 UNIT = new(TNumber.One); + + /// + /// Gets the X value of the vector. + /// + /// The X component of the vector. + public required TNumber X { get; init; } + + /// + /// Gets the Y value of the vector. + /// + /// The Y component of the vector. + public required TNumber Y { get; init; } + + /// + /// Gets the Z value of the vector. + /// + /// The Z component of the vector. + public required TNumber Z { get; init; } + + /// + /// Gets the W value of the vector. + /// + /// The W component of the vector. + public required TNumber W { get; init; } + + /// + /// Gets the R value of the vector (alias for X). + /// + public TNumber R => X; + + /// + /// Gets the G value of the vector (alias for Y). + /// + public TNumber G => Y; + + /// + /// Gets the B value of the vector (alias for Z). + /// + public TNumber B => Z; + + /// + /// Gets the A value of the vector (alias for W). + /// + public TNumber A => W; + + /// + /// Gets the S value of the vector (alias for X). + /// + public TNumber S => X; + + /// + /// Gets the T value of the vector (alias for Y). + /// + public TNumber T => Y; + + /// + /// Gets the P value of the vector (alias for Z). + /// + public TNumber P => Z; + + /// + /// Gets the Q value of the vector (alias for W). + /// + public TNumber Q => W; + + /// + /// Constructs a new using the specified X, Y, Z, and W values. + /// + [SetsRequiredMembers] + public Vector4(TNumber x, TNumber y, TNumber z, TNumber w) { + X = x; + Y = y; + Z = z; + W = w; + } + + /// + /// Constructs a new using the specified value for all components. + /// + [SetsRequiredMembers] + public Vector4(TNumber xyzw) : this(xyzw, xyzw, xyzw, xyzw) { + // ... + } + + /// + /// Normalizes the vector by preserving direction and setting its length to 1. + /// + public Vector4 Normalize() { + TNumber lengthSquared = X * X + Y * Y + Z * Z + W * W; + if (lengthSquared == TNumber.Zero) return ZERO; + double lengthInverted = 1.0 / Math.Sqrt(double.CreateChecked(lengthSquared)); + TNumber lengthConverted = TNumber.CreateChecked(lengthInverted); + return new(X * lengthConverted, Y * lengthConverted, Z * lengthConverted, W * lengthConverted); + } + + /// + /// Converts a from into a from . + /// + public Vector4 ToVector4() { + return new( + float.CreateChecked(X), + float.CreateChecked(Y), + float.CreateChecked(Z), + float.CreateChecked(W) + ); + } + + /// + /// Converts a from into a from . + /// + public static Vector4 FromVector4(Vector4 vector4) { + return new( + TNumber.CreateChecked(vector4.X), + TNumber.CreateChecked(vector4.Y), + TNumber.CreateChecked(vector4.Z), + TNumber.CreateChecked(vector4.W) + ); + } + + /// + /// Converts the vector to a different numeric type. + /// + public static Vector4 ConvertTo(Vector4 vector) where TToNumber : struct, INumber { + return new( + TToNumber.CreateChecked(vector.X), + TToNumber.CreateChecked(vector.Y), + TToNumber.CreateChecked(vector.Z), + TToNumber.CreateChecked(vector.W) + ); + } + + /// + /// Linearly interpolates between two vectors. + /// + public static Vector4 Lerp(Vector4 v1, Vector4 v2, TNumber position) { + return new( + v1.X + (v2.X - v1.X) * position, + v1.Y + (v2.Y - v1.Y) * position, + v1.Z + (v2.Z - v1.Z) * position, + v1.W + (v2.W - v1.W) * position + ); + } + + /// + /// Calculates the dot product of two vectors. + /// + public static TNumber Dot(Vector4 v1, Vector4 v2) { + return v1.X * v2.X + v1.Y * v2.Y + v1.Z * v2.Z + v1.W * v2.W; + } + + /// + /// Calculates the distance between two vectors. + /// + public static TNumber Distance(Vector4 v1, Vector4 v2) { + TNumber dx = v2.X - v1.X; + TNumber dy = v2.Y - v1.Y; + TNumber dz = v2.Z - v1.Z; + TNumber dw = v2.W - v1.W; + double distance = Math.Sqrt(double.CreateChecked(dx * dx + dy * dy + dz * dz + dw * dw)); + return TNumber.CreateChecked(distance); + } + + /// + /// Compares two vectors to determine if the left vector is less than the right vector. + /// + public static bool operator <(Vector4 left, Vector4 right) => left.X < right.X && left.Y < right.Y && left.Z < right.Z && left.W < right.W; + + /// + /// Compares two vectors to determine if the left vector is less than or equal to the right vector. + /// + public static bool operator <=(Vector4 left, Vector4 right) => left.X <= right.X && left.Y <= right.Y && left.Z <= right.Z && left.W <= right.W; + + /// + /// Compares two vectors to determine if the left vector is greater than the right vector. + /// + public static bool operator >(Vector4 left, Vector4 right) => left.X > right.X && left.Y > right.Y && left.Z > right.Z && left.W > right.W; + + /// + /// Compares two vectors to determine if the left vector is greater than or equal to the right vector. + /// + public static bool operator >=(Vector4 left, Vector4 right) => left.X >= right.X && left.Y >= right.Y && left.Z >= right.Z && left.W >= right.W; + + /// + /// Unary plus operator. + /// + public static Vector4 operator +(Vector4 vector) => vector; + + /// + /// Unary negation operator. + /// + public static Vector4 operator -(Vector4 vector) => new(-vector.X, -vector.Y, -vector.Z, -vector.W); + + /// + /// Finds the sum of two vectors. + /// + public static Vector4 operator +(Vector4 left, Vector4 right) => new(left.X + right.X, left.Y + right.Y, left.Z + right.Z, left.W + right.W); + + /// + /// Finds the difference between two vectors. + /// + public static Vector4 operator -(Vector4 left, Vector4 right) => new(left.X - right.X, left.Y - right.Y, left.Z - right.Z, left.W - right.W); + + /// + /// Increments the vector by one. + /// + public static Vector4 operator ++(Vector4 vector) => new(vector.X + TNumber.One, vector.Y + TNumber.One, vector.Z + TNumber.One, vector.W + TNumber.One); + + /// + /// Decrements the vector by one. + /// + public static Vector4 operator --(Vector4 vector) => new(vector.X - TNumber.One, vector.Y - TNumber.One, vector.Z - TNumber.One, vector.W - TNumber.One); + + /// + /// Multiplies the vector by a scalar value. + /// + public static Vector4 operator *(Vector4 vector, TNumber scalar) => new(vector.X * scalar, vector.Y * scalar, vector.Z * scalar, vector.W * scalar); + + /// + public static Vector4 operator *(TNumber scalar, Vector4 vector) => new(vector.X * scalar, vector.Y * scalar, vector.Z * scalar, vector.W * scalar); + + /// + /// Multiplies two vectors together. + /// + public static Vector4 operator *(Vector4 left, Vector4 right) => new(left.X * right.X, left.Y * right.Y, left.Z * right.Z, left.W * right.W); + + /// + /// Divides the vector by a scalar value. + /// + public static Vector4 operator /(Vector4 vector, TNumber scalar) => new(vector.X / scalar, vector.Y / scalar, vector.Z / scalar, vector.W / scalar); + + /// + /// Finds the quotient of two vectors. + /// + public static Vector4 operator /(Vector4 left, Vector4 right) => new(left.X / right.X, left.Y / right.Y, left.Z / right.Z, left.W / right.W); + + } + +} \ No newline at end of file From 1c15697db686547ffa954a28654d6e38ec400a94 Mon Sep 17 00:00:00 2001 From: FireController#1847 Date: Sun, 26 Oct 2025 13:58:11 -0600 Subject: [PATCH 18/20] Add interactions supplementary * Added the `IInputData` interface to Catalyst.Core to represent interaction data. * Also added an associated property for `IInteraction`. * Added various supplementary default interactions and their associated data. * Added device-specific supplementary default interactions for Keyboards and Mice. --- .../Interactions/IInputData.cs | 23 ++ .../Interactions/IInteraction.cs | 6 + .../Interactions/Common/IDigitalInputData.cs | 30 +++ .../Common/IPositionedInputData.cs | 30 +++ .../Interactions/Common/IScrollInputData.cs | 34 +++ .../Interactions/Common/ITextInputData.cs | 34 +++ .../Common/Impl/DigitalInputData.cs | 34 +++ .../Common/Impl/PositionedInputData.cs | 36 +++ .../Common/Impl/ScrollInputData.cs | 38 ++++ .../Interactions/Common/Impl/TextInputData.cs | 34 +++ .../Devices/Keyboard/IKeyboardInputData.cs | 40 ++++ .../Devices/Keyboard/IKeyboardInputDevice.cs | 24 ++ .../Devices/Keyboard/KeyboardInputData.cs | 52 +++++ .../Devices/Keyboard/KeyboardInputKey.cs | 206 ++++++++++++++++++ .../Keyboard/KeyboardInputModifiers.cs | 60 +++++ .../Devices/Mouse/IMouseInputDevice.cs | 24 ++ .../Devices/Mouse/IMouseMovedInputData.cs | 24 ++ .../Devices/Mouse/IMousePressedInputData.cs | 40 ++++ .../Devices/Mouse/IMouseScrollInputData.cs | 24 ++ .../Devices/Mouse/MouseInputButton.cs | 42 ++++ .../Devices/Mouse/MouseMovedInputData.cs | 40 ++++ .../Devices/Mouse/MousePressedInputData.cs | 58 +++++ .../Devices/Mouse/MouseScrollInputData.cs | 43 ++++ 23 files changed, 976 insertions(+) create mode 100644 CatalystUI/Core/CatalystUI.Core/Interactions/IInputData.cs create mode 100644 CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/IDigitalInputData.cs create mode 100644 CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/IPositionedInputData.cs create mode 100644 CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/IScrollInputData.cs create mode 100644 CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/ITextInputData.cs create mode 100644 CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/Impl/DigitalInputData.cs create mode 100644 CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/Impl/PositionedInputData.cs create mode 100644 CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/Impl/ScrollInputData.cs create mode 100644 CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/Impl/TextInputData.cs create mode 100644 CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Keyboard/IKeyboardInputData.cs create mode 100644 CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Keyboard/IKeyboardInputDevice.cs create mode 100644 CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Keyboard/KeyboardInputData.cs create mode 100644 CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Keyboard/KeyboardInputKey.cs create mode 100644 CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Keyboard/KeyboardInputModifiers.cs create mode 100644 CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/IMouseInputDevice.cs create mode 100644 CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/IMouseMovedInputData.cs create mode 100644 CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/IMousePressedInputData.cs create mode 100644 CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/IMouseScrollInputData.cs create mode 100644 CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/MouseInputButton.cs create mode 100644 CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/MouseMovedInputData.cs create mode 100644 CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/MousePressedInputData.cs create mode 100644 CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/MouseScrollInputData.cs diff --git a/CatalystUI/Core/CatalystUI.Core/Interactions/IInputData.cs b/CatalystUI/Core/CatalystUI.Core/Interactions/IInputData.cs new file mode 100644 index 0000000..eb1b390 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Core/Interactions/IInputData.cs @@ -0,0 +1,23 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +namespace Catalyst.Interactions { + + /// + /// Represents the data passed from the input device to the interaction. + /// + public interface IInputData { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Core/Interactions/IInteraction.cs b/CatalystUI/Core/CatalystUI.Core/Interactions/IInteraction.cs index b5a1d22..2fb777f 100644 --- a/CatalystUI/Core/CatalystUI.Core/Interactions/IInteraction.cs +++ b/CatalystUI/Core/CatalystUI.Core/Interactions/IInteraction.cs @@ -22,6 +22,12 @@ public interface IInteraction { /// The input device, or if the origin is unknown. IInputDevice? InputDevice { get; } + /// + /// Gets the input data associated with the interaction. + /// + /// The input data, or if no data is associated. + IInputData? InputData { get; } + } } \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/IDigitalInputData.cs b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/IDigitalInputData.cs new file mode 100644 index 0000000..88f910e --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/IDigitalInputData.cs @@ -0,0 +1,30 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Interactions; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Interactions { + + /// + /// Represents input data for digital input devices (e.g., buttons, keys). + /// + public interface IDigitalInputData : IInputData { + + /// + /// Gets a value indicating whether the digital input is currently activated (pressed) or not. + /// + /// if activated; otherwise, . + bool Activated { get; } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/IPositionedInputData.cs b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/IPositionedInputData.cs new file mode 100644 index 0000000..4aa7aca --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/IPositionedInputData.cs @@ -0,0 +1,30 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Mathematics; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Interactions { + + /// + /// Represents input data that includes positional information. + /// + public interface IPositionedInputData : IInputData { + + /// + /// Gets the position associated with the input data. + /// + /// A representing the position. + Vector2 Position { get; } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/IScrollInputData.cs b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/IScrollInputData.cs new file mode 100644 index 0000000..855057e --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/IScrollInputData.cs @@ -0,0 +1,34 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Mathematics; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Interactions { + + /// + /// Represents input data for scroll input devices (e.g., mouse wheels, touchpad scrolls). + /// + public interface IScrollInputData : IInputData { + + /// + /// Gets the offset of the scroll input in both horizontal and vertical directions. + /// + /// + /// Positive values typically indicate scrolling down/right, + /// while negative values indicate scrolling up/left. + /// + /// A representing the scroll offset. + Vector2 Offset { get; } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/ITextInputData.cs b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/ITextInputData.cs new file mode 100644 index 0000000..93ed4ac --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/ITextInputData.cs @@ -0,0 +1,34 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +// ReSharper disable once CheckNamespace +namespace Catalyst.Interactions { + + /// + /// Represents input data for text input devices (e.g., keyboards, on-screen keyboards). + /// + /// + /// Text input is handled independently of a device since + /// it can originate from various sources. This is preferred + /// for input detection over the device-specific approach + /// because of Dead Keys. + /// + public interface ITextInputData : IInputData { + + /// + /// Gets the text input received from the input device. + /// + /// The text input as a . + string Text { get; } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/Impl/DigitalInputData.cs b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/Impl/DigitalInputData.cs new file mode 100644 index 0000000..ccecac2 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/Impl/DigitalInputData.cs @@ -0,0 +1,34 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +// ReSharper disable once CheckNamespace +namespace Catalyst.Interactions { + + /// + /// A structure conveying digital input data. + /// + public readonly record struct DigitalInputData : IDigitalInputData { + + /// + public required bool Activated { get; init; } + + /// + /// Constructs a new with + /// the specified activation state. + /// + /// The activation state of the digital input. + public DigitalInputData(bool activated) { + Activated = activated; + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/Impl/PositionedInputData.cs b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/Impl/PositionedInputData.cs new file mode 100644 index 0000000..9e488c1 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/Impl/PositionedInputData.cs @@ -0,0 +1,36 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Mathematics; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Interactions { + + /// + /// A structure conveying positioned input data. + /// + public readonly record struct PositionedInputData : IPositionedInputData { + + /// + public required Vector2 Position { get; init; } + + /// + /// Constructs a new with + /// the specified position. + /// + /// The position associated with the input data. + public PositionedInputData(Vector2 position) { + Position = position; + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/Impl/ScrollInputData.cs b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/Impl/ScrollInputData.cs new file mode 100644 index 0000000..1857640 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/Impl/ScrollInputData.cs @@ -0,0 +1,38 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Mathematics; +using System.Diagnostics.CodeAnalysis; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Interactions { + + /// + /// A structure conveying scroll input data. + /// + public readonly record struct ScrollInputData : IScrollInputData { + + /// + public required Vector2 Offset { get; init; } + + /// + /// Constructs a new with + /// the specified offset. + /// + /// The scroll offset. + [SetsRequiredMembers] + public ScrollInputData(Vector2 offset) { + Offset = offset; + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/Impl/TextInputData.cs b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/Impl/TextInputData.cs new file mode 100644 index 0000000..ae6b655 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Common/Impl/TextInputData.cs @@ -0,0 +1,34 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +// ReSharper disable once CheckNamespace +namespace Catalyst.Interactions { + + /// + /// A structure conveying text input data. + /// + public readonly record struct TextInputData : ITextInputData { + + /// + public required string Text { get; init; } + + /// + /// Constructs a new with + /// the specified text. + /// + /// The text associated with the input data. + public TextInputData(string text) { + Text = text; + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Keyboard/IKeyboardInputData.cs b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Keyboard/IKeyboardInputData.cs new file mode 100644 index 0000000..90a9aa3 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Keyboard/IKeyboardInputData.cs @@ -0,0 +1,40 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +// ReSharper disable once CheckNamespace +namespace Catalyst.Interactions.Devices { + + /// + /// Represents keyboard input data. + /// + public interface IKeyboardInputData : IDigitalInputData { + + /// + /// Gets the scan code of the key. + /// + /// The scan code of the key. + int ScanCode { get; } + + /// + /// Gets the interpreted key from the keyboard input. + /// + /// The interpreted key. + KeyboardInputKey Key { get; } + + /// + /// Gets the modifier keys active during the keyboard input. + /// + /// The modifier keys. + KeyboardInputModifiers Modifiers { get; } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Keyboard/IKeyboardInputDevice.cs b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Keyboard/IKeyboardInputDevice.cs new file mode 100644 index 0000000..7e8c8db --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Keyboard/IKeyboardInputDevice.cs @@ -0,0 +1,24 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +// ReSharper disable once CheckNamespace +namespace Catalyst.Interactions.Devices { + + /// + /// Represents a keyboard input device. + /// + public interface IKeyboardInputDevice : IInputDevice { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Keyboard/KeyboardInputData.cs b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Keyboard/KeyboardInputData.cs new file mode 100644 index 0000000..37f5f5b --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Keyboard/KeyboardInputData.cs @@ -0,0 +1,52 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System.Diagnostics.CodeAnalysis; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Interactions.Devices { + + /// + /// A structure conveying keyboard input data. + /// + public readonly record struct KeyboardInputData : IKeyboardInputData { + + /// + public required bool Activated { get; init; } + + /// + public required int ScanCode { get; init; } + + /// + public required KeyboardInputKey Key { get; init; } + + /// + public required KeyboardInputModifiers Modifiers { get; init; } + + /// + /// Constructs a new with + /// the specified activation state, scan code, key, and modifiers. + /// + /// Whether the key is activated (pressed) or not. + /// The scan code of the key. + /// The interpreted key. + /// The modifier keys. + [SetsRequiredMembers] + public KeyboardInputData(bool activated, int scanCode, KeyboardInputKey key, KeyboardInputModifiers modifiers) { + Activated = activated; + ScanCode = scanCode; + Key = key; + Modifiers = modifiers; + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Keyboard/KeyboardInputKey.cs b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Keyboard/KeyboardInputKey.cs new file mode 100644 index 0000000..8ed7c47 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Keyboard/KeyboardInputKey.cs @@ -0,0 +1,206 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +// ReSharper disable once CheckNamespace +namespace Catalyst.Interactions.Devices { + + /// + /// A list of keyboard keys. + /// + /// + /// + /// The design of this enum is to support + /// as many keys as possible, but there will + /// be limitations due to the nature of different + /// languages and keyboard formats. + /// + /// + /// The focus is to map keys to their associated + /// Unicode value under an English, QWERTY U.S. Layout. + /// However, implementing the actual translation + /// to characters will depend on the user's implementation. + /// + /// + /// Some keys, such as the F1-F24 keys, are mapped + /// to the private use Unicode space. If an unsupported + /// key is encountered, it should be mapped to the + /// 0xF900-0xF9FF range. CatalystUI reserves the + /// space of 0xF700-0xF8FF for future use. + /// + /// + public enum KeyboardInputKey { + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + + // NULL + Null = 0, + + // Function keys (F1-F24) + F1 = 0xF700, + F2 = 0xF701, + F3 = 0xF702, + F4 = 0xF703, + F5 = 0xF704, + F6 = 0xF705, + F7 = 0xF706, + F8 = 0xF707, + F9 = 0xF708, + F10 = 0xF709, + F11 = 0xF70A, + F12 = 0xF70B, + F13 = 0xF70C, + F14 = 0xF70D, + F15 = 0xF70E, + F16 = 0xF70F, + F17 = 0xF710, + F18 = 0xF711, + F19 = 0xF712, + F20 = 0xF713, + F21 = 0xF714, + F22 = 0xF715, + F23 = 0xF716, + F24 = 0xF717, + + // Number keys + Zero = 0x0030, + One = 0x0031, + Two = 0x0032, + Three = 0x0033, + Four = 0x0034, + Five = 0x0035, + Six = 0x0036, + Seven = 0x0037, + Eight = 0x0038, + Nine = 0x0039, + + // Alphanumeric keys + A = 0x0041, + B = 0x0042, + C = 0x0043, + D = 0x0044, + E = 0x0045, + F = 0x0046, + G = 0x0047, + H = 0x0048, + I = 0x0049, + J = 0x004A, + K = 0x004B, + L = 0x004C, + M = 0x004D, + N = 0x004E, + O = 0x004F, + P = 0x0050, + Q = 0x0051, + R = 0x0052, + S = 0x0053, + T = 0x0054, + U = 0x0055, + V = 0x0056, + W = 0x0057, + X = 0x0058, + Y = 0x0059, + Z = 0x005A, + + // Symbols + Space = 0x0020, + Apostrophe = 0x0027, + Comma = 0x002C, + Dash = 0x002D, + Period = 0x002E, + Slash = 0x002F, + Semicolon = 0x003B, + Equals = 0x003D, + LeftBracket = 0x005B, + Backslash = 0x005C, + RightBracket = 0x005D, + GraveAccent = 0x0060, + + // Special characters (with Unicode mappings) + Backspace = 0x0008, + Tab = 0x0009, + Enter = 0x000D, + Escape = 0x001B, + Delete = 0x007F, + + // Special characters (without Unicode mappings) + PrintScreen = 0xF800, + ScrollLock = 0xF801, + PauseBreak = 0xF802, + Insert = 0xF803, + CapsLock = 0xF804, + Home = 0xF805, + End = 0xF806, + PageUp = 0xF807, + PageDown = 0xF808, + NumLock = 0xF809, + + // Arrow keys + ArrowLeft = 0x2190, + ArrowUp = 0x2191, + ArrowRight = 0x2192, + ArrowDown = 0x2193, + + // Modifier keys + LeftShift = 0xF820, + LeftControl = 0xF821, + LeftSuper = 0xF822, + LeftWindows = LeftSuper, + LeftCommand = LeftSuper, + LeftAlt = 0xF823, + RightShift = 0xF830, + RightControl = 0xF831, + RightSuper = 0xF832, + RightWindows = RightSuper, + RightCommand = RightSuper, + RightAlt = 0xF833, + Function = 0xF834, + Menu = 0xF835, + + // Number pad + NumpadDivide = 0xF850, + NumpadMultiply = 0xF851, + NumpadSubtract = 0xF852, + NumpadAdd = 0xF853, + NumpadEnter = 0xF854, + NumpadDecimal = 0xF855, + NumpadZero = 0xF860, + NumpadOne = 0xF861, + NumpadTwo = 0xF862, + NumpadThree = 0xF863, + NumpadFour = 0xF864, + NumpadFive = 0xF865, + NumpadSix = 0xF866, + NumpadSeven = 0xF867, + NumpadEight = 0xF868, + NumpadNine = 0xF869, + + // Media controls + MediaPlay = 0xF880, + MediaPause = 0xF881, + MediaStop = 0xF882, + MediaNext = 0xF883, + MediaPrevious = 0xF884, + VolumeMute = 0xF885, + VolumeUp = 0xF886, + VolumeDown = 0xF887, + + // Computer controls + Help = 0xF890, + Print = 0xF891, + Power = 0xF892, + Sleep = 0xF893, + Wake = 0xF894, + +#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Keyboard/KeyboardInputModifiers.cs b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Keyboard/KeyboardInputModifiers.cs new file mode 100644 index 0000000..a7899f5 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Keyboard/KeyboardInputModifiers.cs @@ -0,0 +1,60 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Interactions.Devices { + + /// + /// A list of keyboard modifier keys. + /// + [Flags] + public enum KeyboardInputModifiers { + + /// + /// No modifier keys. + /// + None = 0, + + /// + /// Shift modifier key. + /// + Shift = 1 << 1, + + /// + /// Control modifier key. + /// + Control = 1 << 2, + + /// + /// Alt modifier key. + /// + Alt = 1 << 3, + + /// + /// (Linux/Other) Super modifier key. + /// + Super = 1 << 4, + + /// + /// (Windows) Windows modifier key. + /// + Windows = Super, + + /// + /// (MacOS) Command modifier key. + /// + Command = Super + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/IMouseInputDevice.cs b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/IMouseInputDevice.cs new file mode 100644 index 0000000..a11b863 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/IMouseInputDevice.cs @@ -0,0 +1,24 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +// ReSharper disable once CheckNamespace +namespace Catalyst.Interactions.Devices { + + /// + /// Represents a mouse input device. + /// + public interface IMouseInputDevice : IInputDevice { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/IMouseMovedInputData.cs b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/IMouseMovedInputData.cs new file mode 100644 index 0000000..b52cf36 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/IMouseMovedInputData.cs @@ -0,0 +1,24 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +// ReSharper disable once CheckNamespace +namespace Catalyst.Interactions.Devices { + + /// + /// Represents mouse-move input data. + /// + public interface IMouseMovedInputData : IPositionedInputData { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/IMousePressedInputData.cs b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/IMousePressedInputData.cs new file mode 100644 index 0000000..fcb247e --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/IMousePressedInputData.cs @@ -0,0 +1,40 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +// ReSharper disable once CheckNamespace +namespace Catalyst.Interactions.Devices { + + /// + /// Represents mouse-press input data. + /// + public interface IMousePressedInputData : IPositionedInputData, IDigitalInputData { + + /// + /// Gets the mouse button that was pressed. + /// + /// The mouse button. + MouseInputButton Button { get; } + + /// + /// Gets the identifier of the button that was pressed. + /// + /// The button identifier. + int ButtonId { get; } + + /// + /// Gets the modifier keys active during the mouse press. + /// + /// The modifier keys. + KeyboardInputModifiers Modifiers { get; } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/IMouseScrollInputData.cs b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/IMouseScrollInputData.cs new file mode 100644 index 0000000..867221f --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/IMouseScrollInputData.cs @@ -0,0 +1,24 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +// ReSharper disable once CheckNamespace +namespace Catalyst.Interactions.Devices { + + /// + /// Represents mouse-scroll input data. + /// + public interface IMouseScrollInputData : IPositionedInputData, IScrollInputData { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/MouseInputButton.cs b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/MouseInputButton.cs new file mode 100644 index 0000000..7bc3ec6 --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/MouseInputButton.cs @@ -0,0 +1,42 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +// ReSharper disable once CheckNamespace +namespace Catalyst.Interactions.Devices { + + /// + /// A list of mouse buttons. + /// + public enum MouseInputButton { + + /// + /// The primary mouse button (typically left click). + /// + PrimaryButton, + + /// + /// The secondary mouse button (typically right click). + /// + SecondaryButton, + + /// + /// The tertiary mouse button (typically middle click). + /// + TertiaryButton, + + /// + /// Any other button on the mouse. + /// + OtherButton + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/MouseMovedInputData.cs b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/MouseMovedInputData.cs new file mode 100644 index 0000000..36a56ef --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/MouseMovedInputData.cs @@ -0,0 +1,40 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + + + +using System.Diagnostics.CodeAnalysis; +using Catalyst.Mathematics; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Interactions.Devices { + + /// + /// A structure conveying mouse moved input data. + /// + public readonly record struct MouseMovedInputData : IMouseMovedInputData { + + /// + public required Vector2 Position { get; init; } + + /// + /// Constructs a new with + /// the specified position. + /// + /// The position of the mouse. + [SetsRequiredMembers] + public MouseMovedInputData(Vector2 position) { + Position = position; + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/MousePressedInputData.cs b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/MousePressedInputData.cs new file mode 100644 index 0000000..9403a3d --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/MousePressedInputData.cs @@ -0,0 +1,58 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Mathematics; +using System.Diagnostics.CodeAnalysis; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Interactions.Devices { + + /// + /// A structure conveying mouse pressed input data. + /// + public readonly record struct MousePressedInputData : IMousePressedInputData { + + /// + public required Vector2 Position { get; init; } + + /// + public required bool Activated { get; init; } + + /// + public required MouseInputButton Button { get; init; } + + /// + public required int ButtonId { get; init; } + + /// + public required KeyboardInputModifiers Modifiers { get; init; } + + /// + /// Constructs a new with + /// the specified position, activation state, button, button ID, and modifiers. + /// + /// The position of the mouse. + /// The activation state of the button. + /// The mouse button. + /// The button identifier. + /// The modifier keys. + [SetsRequiredMembers] + public MousePressedInputData(Vector2 position, bool activated, MouseInputButton button, int buttonId, KeyboardInputModifiers modifiers) { + Position = position; + Activated = activated; + Button = button; + ButtonId = buttonId; + Modifiers = modifiers; + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/MouseScrollInputData.cs b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/MouseScrollInputData.cs new file mode 100644 index 0000000..955c05e --- /dev/null +++ b/CatalystUI/Core/CatalystUI.Supplementary/Interactions/Devices/Mouse/MouseScrollInputData.cs @@ -0,0 +1,43 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Mathematics; +using System.Diagnostics.CodeAnalysis; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Interactions.Devices { + + /// + /// A structure conveying mouse scroll input data. + /// + public readonly record struct MouseScrollInputData : IMouseScrollInputData { + + /// + public required Vector2 Position { get; init; } + + /// + public required Vector2 Offset { get; init; } + + /// + /// Constructs a new with + /// the specified position and offset. + /// + /// The position of the mouse. + /// The scroll offset. + [SetsRequiredMembers] + public MouseScrollInputData(Vector2 position, Vector2 offset) { + Position = position; + Offset = offset; + } + + } + +} \ No newline at end of file From 2a8b6f2b5e5e7a94e47e7d6e090193ca93e845d0 Mon Sep 17 00:00:00 2001 From: FireController#1847 Date: Sun, 26 Oct 2025 14:54:11 -0600 Subject: [PATCH 19/20] Add the Arcane project * Added the Arcane module to the solution. * Added various new solution folders. * Added the INI Arcane module implementation from previous project iterations. --- .scripts/Setup.ps1 | 22 +- CatalystUI/CatalystUI.sln | 25 +++ .../CatalystUI.Modules.Arcane.Core.csproj | 24 +++ .../IArcaneDomain.cs | 32 +++ .../IIniDataConnector.cs | 26 +++ .../Types/FileSchema/IFileSchema.cs | 39 ++++ .../Types/FileSchema/IFileSchemaFieldInfo.cs | 63 ++++++ .../FileSchema/IFileSchemaSchemaField.cs | 32 +++ .../IFileSchemaSchemaFieldConstant.cs | 32 +++ .../Types/Ini/Components/IIniComponent.cs | 88 ++++++++ .../Ini/Components/IIniSectionComponent.cs | 64 ++++++ .../Types/Ini/IIniComponentsLayer.cs | 41 ++++ .../Types/Ini/IIniDataLayer.cs | 26 +++ .../Types/Ini/IIniDomain.cs | 26 +++ .../Types/Ini/IIniParserConnector.cs | 41 ++++ .../Types/Ini/IIniSemanticsLayer.cs | 41 ++++ .../Ini/Semantics/IIniSectionSemantic.cs | 47 +++++ .../Types/Ini/Semantics/IIniSemantic.cs | 59 ++++++ .../CatalystUI.Modules.Arcane.Ini.csproj | 23 +++ .../Components/IniComponent.cs | 195 ++++++++++++++++++ .../Components/IniSectionComponent.cs | 147 +++++++++++++ .../CatalystAppBuilderExtensions.cs | 53 +++++ .../IniComponentsLayer.cs | 33 +++ .../IniDataConnector.cs | 54 +++++ .../IniParserConnector.cs | 55 +++++ .../IniSemanticsLayer.cs | 33 +++ .../Semantics/IniSectionSemantic.cs | 77 +++++++ .../Semantics/IniSemantic.cs | 94 +++++++++ 28 files changed, 1489 insertions(+), 3 deletions(-) create mode 100644 CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/CatalystUI.Modules.Arcane.Core.csproj create mode 100644 CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/IArcaneDomain.cs create mode 100644 CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/IIniDataConnector.cs create mode 100644 CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/FileSchema/IFileSchema.cs create mode 100644 CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/FileSchema/IFileSchemaFieldInfo.cs create mode 100644 CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/FileSchema/IFileSchemaSchemaField.cs create mode 100644 CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/FileSchema/IFileSchemaSchemaFieldConstant.cs create mode 100644 CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/Components/IIniComponent.cs create mode 100644 CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/Components/IIniSectionComponent.cs create mode 100644 CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/IIniComponentsLayer.cs create mode 100644 CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/IIniDataLayer.cs create mode 100644 CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/IIniDomain.cs create mode 100644 CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/IIniParserConnector.cs create mode 100644 CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/IIniSemanticsLayer.cs create mode 100644 CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/Semantics/IIniSectionSemantic.cs create mode 100644 CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/Semantics/IIniSemantic.cs create mode 100644 CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/CatalystUI.Modules.Arcane.Ini.csproj create mode 100644 CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/Components/IniComponent.cs create mode 100644 CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/Components/IniSectionComponent.cs create mode 100644 CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/Extensions/CatalystAppBuilderExtensions.cs create mode 100644 CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/IniComponentsLayer.cs create mode 100644 CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/IniDataConnector.cs create mode 100644 CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/IniParserConnector.cs create mode 100644 CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/IniSemanticsLayer.cs create mode 100644 CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/Semantics/IniSectionSemantic.cs create mode 100644 CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/Semantics/IniSemantic.cs diff --git a/.scripts/Setup.ps1 b/.scripts/Setup.ps1 index 8f2602c..ee5587a 100644 --- a/.scripts/Setup.ps1 +++ b/.scripts/Setup.ps1 @@ -53,7 +53,24 @@ $projectsList = @( @{ Folder = "Core"; Name = "CatalystUI.Debug" }, @{ Folder = "Core"; Name = "CatalystUI.Supplementary" } ) - PromptIgnore = $true + PromptIgnore = $false + Depends = @() + }, + @{ + Module = "Arcane" + Projects = @( + @{ Folder = "Modules/Arcane"; Name = "CatalystUI.Modules.Arcane.Core" } + ) + PromptIgnore = $false + Depends = @("Core") + }, + @{ + Module = "Arcane.Ini" + Project = @( + @{ Folder = "Modules/Arcane"; Name = "CatalystUI.Modules.Arcane.Ini" } + ) + PromptIgnore = $false + Depends = @("Arcane") } ) @@ -61,8 +78,7 @@ $projectsList = @( $promptOptions = @("All") + ( $projectsList | Where-Object { -not $_.PromptIgnore -and $_.Module -ne "All" } | - Select-Object -ExpandProperty Module -Unique | - Sort-Object + Select-Object -ExpandProperty Module -Unique ) # Prompt user for module selection diff --git a/CatalystUI/CatalystUI.sln b/CatalystUI/CatalystUI.sln index 4ff5395..0b50ed1 100644 --- a/CatalystUI/CatalystUI.sln +++ b/CatalystUI/CatalystUI.sln @@ -24,6 +24,18 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CatalystUI.Supplementary", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CatalystUI.Mathematics", "Core\CatalystUI.Mathematics\CatalystUI.Mathematics.csproj", "{E2B3A13C-9AE6-44D8-8456-58723ABBC343}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules", "Modules", "{9C3F6A00-82F5-4900-9D6C-07ACBBAAE823}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Crystal", "Crystal", "{41BEF490-7005-4D10-9958-22D636F9DE38}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Arcane", "Arcane", "{C8B02B42-826B-4EDE-B72F-F4F97C1A088D}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Veilstone", "Veilstone", "{0E1F2B64-37D9-4C24-9CED-9A44D7CDBB8C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CatalystUI.Modules.Arcane.Ini", "Modules\Arcane\CatalystUI.Modules.Arcane.Ini\CatalystUI.Modules.Arcane.Ini.csproj", "{C02600D7-087B-4190-9B47-F15184C19B2D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CatalystUI.Modules.Arcane.Core", "Modules\Arcane\CatalystUI.Modules.Arcane.Core\CatalystUI.Modules.Arcane.Core.csproj", "{047744B0-87DC-4808-99C4-5AC1F8A1EB4D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -70,6 +82,14 @@ Global {E2B3A13C-9AE6-44D8-8456-58723ABBC343}.Debug|Any CPU.Build.0 = Debug|Any CPU {E2B3A13C-9AE6-44D8-8456-58723ABBC343}.Release|Any CPU.ActiveCfg = Release|Any CPU {E2B3A13C-9AE6-44D8-8456-58723ABBC343}.Release|Any CPU.Build.0 = Release|Any CPU + {C02600D7-087B-4190-9B47-F15184C19B2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C02600D7-087B-4190-9B47-F15184C19B2D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C02600D7-087B-4190-9B47-F15184C19B2D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C02600D7-087B-4190-9B47-F15184C19B2D}.Release|Any CPU.Build.0 = Release|Any CPU + {047744B0-87DC-4808-99C4-5AC1F8A1EB4D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {047744B0-87DC-4808-99C4-5AC1F8A1EB4D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {047744B0-87DC-4808-99C4-5AC1F8A1EB4D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {047744B0-87DC-4808-99C4-5AC1F8A1EB4D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {68F496AC-9438-40F1-9DF8-97363033D661} = {7EC51871-49A8-4991-BDAF-F43A4E9B8C9D} @@ -82,5 +102,10 @@ Global {39CD2850-CB5B-4F3C-81AB-9430506F3BD7} = {7EC51871-49A8-4991-BDAF-F43A4E9B8C9D} {33B1D211-9C3A-4AA8-95DE-75D9122CC968} = {7EC51871-49A8-4991-BDAF-F43A4E9B8C9D} {E2B3A13C-9AE6-44D8-8456-58723ABBC343} = {7EC51871-49A8-4991-BDAF-F43A4E9B8C9D} + {41BEF490-7005-4D10-9958-22D636F9DE38} = {9C3F6A00-82F5-4900-9D6C-07ACBBAAE823} + {C8B02B42-826B-4EDE-B72F-F4F97C1A088D} = {9C3F6A00-82F5-4900-9D6C-07ACBBAAE823} + {0E1F2B64-37D9-4C24-9CED-9A44D7CDBB8C} = {9C3F6A00-82F5-4900-9D6C-07ACBBAAE823} + {C02600D7-087B-4190-9B47-F15184C19B2D} = {C8B02B42-826B-4EDE-B72F-F4F97C1A088D} + {047744B0-87DC-4808-99C4-5AC1F8A1EB4D} = {C8B02B42-826B-4EDE-B72F-F4F97C1A088D} EndGlobalSection EndGlobal diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/CatalystUI.Modules.Arcane.Core.csproj b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/CatalystUI.Modules.Arcane.Core.csproj new file mode 100644 index 0000000..dd54f63 --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/CatalystUI.Modules.Arcane.Core.csproj @@ -0,0 +1,24 @@ + + + + + + Catalyst.Modules.Arcane + Catalyst.Modules.Arcane + + + CatalystUI Arcane Core + 1.0.0 + alpha.1 + CatalystUI LLC + Core API for the Arcane subset of modules provided by the CatalystUI library. + CatalystUI,Arcane,core,file,filesystem + + + + + + + + + \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/IArcaneDomain.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/IArcaneDomain.cs new file mode 100644 index 0000000..580db53 --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/IArcaneDomain.cs @@ -0,0 +1,32 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Domains; + +namespace Catalyst.Modules.Arcane { + + /// + /// Represents the Arcane domain in the CatalystUI model. + /// + /// + /// Arcane is a subset of modules for CatalystUI which + /// are designed to work with files. These modules + /// provide functionality for reading, writing, and + /// manipulating various file formats, as well as + /// integrating file-based data into CatalystUI applications. + /// + public interface IArcaneDomain : ISymbolicDomain { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/IIniDataConnector.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/IIniDataConnector.cs new file mode 100644 index 0000000..cce5dcc --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/IIniDataConnector.cs @@ -0,0 +1,26 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Supplementary; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Modules.Arcane.Ini { + + /// + /// Represents a data handler for INI configuration files. + /// + public interface IIniDataConnector : IFileDataConnector { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/FileSchema/IFileSchema.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/FileSchema/IFileSchema.cs new file mode 100644 index 0000000..5d3d42f --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/FileSchema/IFileSchema.cs @@ -0,0 +1,39 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Modules.Arcane { + + /// + /// Represents a file as defined by a file's associated schema. + /// + /// + /// Essentially acts as a collection of fields for any form of file + /// which can be defined by a schema. It simplifies the process of + /// working with files by providing a strongly-typed interface + /// to access fields by their identifiers. + /// + /// An enum type which represents all possible fields in the file schema. + public interface IFileSchema where TField : Enum { + + /// + /// Gets a field from the file by its identifier. + /// + /// The field identifier. + /// The type of the value stored in the field. + /// The field instance. + IFileSchemaField GetField(TField field); + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/FileSchema/IFileSchemaFieldInfo.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/FileSchema/IFileSchemaFieldInfo.cs new file mode 100644 index 0000000..d47f9b4 --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/FileSchema/IFileSchemaFieldInfo.cs @@ -0,0 +1,63 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Modules.Arcane { + + /// + /// Represents a file's field descriptor as defined by a file's associated schema. + /// + /// An enum type which represents all possible fields in the file schema. + public interface IFileSchemaFieldInfo where TField : Enum { + + /// + /// The field identifier. + /// + /// An enum value representing the field. + static abstract TField Field { get; } + + /// + /// The offset in bytes from the start of the file where the field begins. + /// + /// The offset in bytes, or to represent a variable offset. + static abstract uint? Offset { get; } + + /// + /// The size in bytes of the field. + /// + /// The size in bytes, or to represent a variable size. + static abstract uint? Size { get; } + + /// + /// An optional human-friendly name for the field. + /// + /// + /// Keep in mind the value provided here will always be embedded in compiled builds + /// for end-users, so it should be concise and meaningful if provided. + /// + /// The human-friendly name, or if not specified. + static abstract string? Name { get; } + + /// + /// An optional human-friendly description of the field. + /// + /// + /// Keep in mind the value provided here will always be embedded in compiled builds + /// for end-users, so it should be concise and meaningful if provided. + /// + /// The human-friendly description, or if not specified. + static abstract string? Description { get; } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/FileSchema/IFileSchemaSchemaField.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/FileSchema/IFileSchemaSchemaField.cs new file mode 100644 index 0000000..0d8c54c --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/FileSchema/IFileSchemaSchemaField.cs @@ -0,0 +1,32 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Modules.Arcane { + + /// + /// Represents a file's field as defined by a file's associated schema. + /// + /// An enum type which represents all possible fields in the file schema. + /// The type of the value stored in the field. + public interface IFileSchemaField : IFileSchemaFieldInfo where TField : Enum { + + /// + /// Gets the value of the field. + /// + /// The field's value. + TValue? Value { get; } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/FileSchema/IFileSchemaSchemaFieldConstant.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/FileSchema/IFileSchemaSchemaFieldConstant.cs new file mode 100644 index 0000000..931c89b --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/FileSchema/IFileSchemaSchemaFieldConstant.cs @@ -0,0 +1,32 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Modules.Arcane { + + /// + /// Represents a file's field which contains a constant value as defined by a file's associated schema. + /// + /// An enum type which represents all possible fields in the file schema. + /// The type of the value stored in the field. + public interface IFileSchemaFieldConstant : IFileSchemaFieldInfo where TField : Enum { + + /// + /// Gets the value of the field. + /// + /// The field's value. + static abstract TValue? Value { get; } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/Components/IIniComponent.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/Components/IIniComponent.cs new file mode 100644 index 0000000..4431458 --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/Components/IIniComponent.cs @@ -0,0 +1,88 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Modules.Arcane.Ini { + + /// + /// Represents a component for an INI configuration file. + /// + public interface IIniComponent { + + /// + /// Gets the global key-value pairs in the INI file. + /// + /// A read-only dictionary of the global entries. + IReadOnlyDictionary Entries { get; } + + /// + /// Gets the sections defined in the INI file. + /// + /// A read-only list of the INI sections. + IReadOnlyList Sections { get; } + + /// + /// Sets the value for the specified global key in the INI file. + /// + /// The key of the global entry to set. + /// The value to associate with the key. + /// Thrown if the provided key is or empty. + void SetValue(string key, string? value); + + /// + /// Attempts to get the value associated with the specified global key in the INI file. + /// + /// The key of the global entry to retrieve. + /// The value associated with the key, if found; otherwise, . + /// if the key exists; otherwise, . + /// Thrown if the provided key is or empty. + bool TryGetValue(string key, [NotNullWhen(true)] out string? value); + + /// + /// Attempts to remove the entry with the specified global key from the INI file. + /// + /// The key of the entry to remove. + /// The previously associated value, if the key was found; otherwise, . + /// if the key existed and was removed; otherwise, . + bool TryRemoveValue(string key, [NotNullWhen(true)] out string? removed); + + /// + /// Sets or adds a section in the INI file. If a section with the same name already exists, it will be replaced. + /// + /// The section to set or add. + /// Thrown if the provided section is . + void SetSection(IIniSectionComponent section); + + /// + /// Attempts to get a section with the specified name. + /// + /// The name of the section to retrieve. + /// The section with the specified name, if found; otherwise, . + /// if the section exists; otherwise, . + /// Thrown if the provided name is or empty. + bool TryGetSection(string name, [NotNullWhen(true)] out IIniSectionComponent? section); + + /// + /// Attempts to remove the section with the specified name from the INI file. + /// + /// The name of the section to remove. + /// The previously associated section, if the section was found; otherwise, . + /// if the section existed and was removed; otherwise, . + /// Thrown if the provided name is or empty. + bool TryRemoveSection(string name, [NotNullWhen(true)] out IIniSectionComponent? removed); + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/Components/IIniSectionComponent.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/Components/IIniSectionComponent.cs new file mode 100644 index 0000000..7cabb37 --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/Components/IIniSectionComponent.cs @@ -0,0 +1,64 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Modules.Arcane.Ini { + + /// + /// Represents a component for a section of an INI configuration file. + /// + public interface IIniSectionComponent { + + /// + /// Gets or sets the name of the INI section. + /// + /// The INI section's name. + string Name { get; set; } + + /// + /// Gets the key-value pairs within the INI section. + /// + /// A read-only dictionary of the section's entries. + IReadOnlyDictionary Entries { get; } + + /// + /// Sets the value for the specified key in the INI section. + /// + /// The key of the entry to set. + /// The value to associate with the key. Use to remove the entry. + /// Thrown if the provided key is or empty. + void SetValue(string key, string? value); + + /// + /// Attempts to get the value associated with the specified key in the INI section. + /// + /// The key of the entry to retrieve. + /// The value associated with the key, if found; otherwise, . + /// if the key exists; otherwise, . + /// Thrown if the provided key is or empty. + bool TryGetValue(string key, [NotNullWhen(true)] out string? value); + + /// + /// Attempts to remove the entry with the specified key from the INI section. + /// + /// The key of the entry to remove. + /// The previously associated value, if the key was found; otherwise, . + /// if the key existed and was removed; otherwise, . + /// Thrown if the provided key is or empty. + bool TryRemoveValue(string key, [NotNullWhen(true)] out string? removed); + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/IIniComponentsLayer.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/IIniComponentsLayer.cs new file mode 100644 index 0000000..fc0283b --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/IIniComponentsLayer.cs @@ -0,0 +1,41 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Layers; +using System.Collections.Generic; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Modules.Arcane.Ini { + + /// + /// Represents the components layer for INI configuration files. + /// + public interface IIniComponentsLayer : IComponentsLayer { + + /// + /// Creates an INI components representation from the provided entries and sections. + /// + /// The global key-value pairs in the INI file, or if there are none. + /// The sections within the INI file, or if there are none. + /// A new instance of an from the provided data. + IIniComponent CreateComponent(IReadOnlyDictionary? entries = null, IReadOnlyList? sections = null); + + /// + /// Creates a section components representation from the provided name and entries. + /// + /// The name of the section. + /// The key-value pairs within the section, or if there are none. + /// A new instance of an from the provided data. + IIniSectionComponent CreateSectionComponent(string name, IReadOnlyDictionary? entries = null); + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/IIniDataLayer.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/IIniDataLayer.cs new file mode 100644 index 0000000..4f224e8 --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/IIniDataLayer.cs @@ -0,0 +1,26 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Supplementary; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Modules.Arcane.Ini { + + /// + /// Represents the data layer for INI configuration files. + /// + public interface IIniDataLayer : IFileInfoDataLayer { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/IIniDomain.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/IIniDomain.cs new file mode 100644 index 0000000..72f33e2 --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/IIniDomain.cs @@ -0,0 +1,26 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Domains; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Modules.Arcane.Ini { + + /// + /// Represents a domain for INI configuration files. + /// + public interface IIniDomain : ISymbolicDomain { + + // ... + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/IIniParserConnector.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/IIniParserConnector.cs new file mode 100644 index 0000000..50f55ba --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/IIniParserConnector.cs @@ -0,0 +1,41 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Connectors; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Modules.Arcane.Ini { + + /// + /// Represents a parser connector for INI configuration files. + /// + /// + /// Provides mutation from INI semantic types to INI component types. + /// + public interface IIniParserConnector : IParserConnector { + + /// + /// Mutates the provided INI semantic representation into an INI components representation. + /// + /// The INI semantic representation to mutate. + /// A new instance of an representing the provided semantic data. + IIniComponent ToComponent(IIniSemantic semantic); + + /// + /// Mutates the provided INI components representation into an INI semantic representation. + /// + /// The INI components representation to mutate. + /// A new instance of an representing the provided components data. + IIniSemantic ToSemantic(IIniComponent component); + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/IIniSemanticsLayer.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/IIniSemanticsLayer.cs new file mode 100644 index 0000000..12cca60 --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/IIniSemanticsLayer.cs @@ -0,0 +1,41 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Layers; +using System.Collections.Generic; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Modules.Arcane.Ini { + + /// + /// Represents the semantics layer for INI configuration files. + /// + public interface IIniSemanticsLayer : ISemanticsLayer { + + /// + /// Creates an INI semantics representation from the provided entries and sections. + /// + /// The global key-value pairs in the INI file. + /// The sections within the INI file. + /// A new instance of an from the provided data. + IIniSemantic CreateSemantic(IReadOnlyDictionary entries, IReadOnlyList sections); + + /// + /// Creates a section semantics representation from the provided name and entries. + /// + /// The name of the section. + /// The key-value pairs within the section. + /// A new instance of an from the provided data. + IIniSectionSemantic CreateSectionSemantic(string name, IReadOnlyDictionary entries); + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/Semantics/IIniSectionSemantic.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/Semantics/IIniSectionSemantic.cs new file mode 100644 index 0000000..7509719 --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/Semantics/IIniSectionSemantic.cs @@ -0,0 +1,47 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Modules.Arcane.Ini { + + /// + /// Represents a semantic for a section of an INI configuration file. + /// + public interface IIniSectionSemantic { + + /// + /// Gets the name of the INI section. + /// + /// The INI section's name. + string Name { get; } + + /// + /// Gets the key-value pairs within the INI section. + /// + /// A read-only dictionary of the section's entries. + IReadOnlyDictionary Entries { get; } + + /// + /// Attempts to get the value associated with the specified key in the INI section. + /// + /// The key of the entry to retrieve. + /// The value associated with the key, if found; otherwise, . + /// if the key exists; otherwise, . + /// Thrown if the provided key is or empty. + bool TryGetValue(string key, [NotNullWhen(true)] out string? value); + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/Semantics/IIniSemantic.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/Semantics/IIniSemantic.cs new file mode 100644 index 0000000..1dd825e --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Core/Types/Ini/Semantics/IIniSemantic.cs @@ -0,0 +1,59 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Modules.Arcane.Ini { + + /// + /// Represents a semantic for an INI configuration file. + /// + public interface IIniSemantic { + + /// + /// Gets the global key-value pairs in the INI file. + /// + /// + /// A global entry is one defined outside any section. + /// + /// A read-only dictionary of the global entries. + IReadOnlyDictionary Entries { get; } + + /// + /// Gets the sections defined in the INI file. + /// + /// A read-only list of the INI sections. + IReadOnlyList Sections { get; } + + /// + /// Attempts to get the value associated with the specified global key in the INI file. + /// + /// The key of the global entry to retrieve. + /// The value associated with the key, if found; otherwise, . + /// if the key exists; otherwise, . + /// Thrown if the provided key is or empty. + bool TryGetValue(string key, [NotNullWhen(true)] out string? value); + + /// + /// Attempts to get a section with the specified name. + /// + /// The name of the section to retrieve. + /// The section with the specified name, if found; otherwise, . + /// if the section exists; otherwise, . + /// Thrown if the provided name is or empty. + bool TryGetSection(string name, [NotNullWhen(true)] out IIniSectionSemantic? section); + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/CatalystUI.Modules.Arcane.Ini.csproj b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/CatalystUI.Modules.Arcane.Ini.csproj new file mode 100644 index 0000000..b104188 --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/CatalystUI.Modules.Arcane.Ini.csproj @@ -0,0 +1,23 @@ + + + + + + Catalyst.Modules.Arcane.Ini + Catalyst.Modules.Arcane.Ini + + + CatalystUI Arcane – .INI Configuration File Format + 1.0.0 + beta.2 + CatalystUI LLC + .INI API for the Arcane subset of modules provided by the CatalystUI library. + CatalystUI,Arcane,ini,configuration,file,format + + + + + + + + \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/Components/IniComponent.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/Components/IniComponent.cs new file mode 100644 index 0000000..f9bf257 --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/Components/IniComponent.cs @@ -0,0 +1,195 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; +using System.Threading; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Modules.Arcane.Ini { + + /// + /// A mutable component for an INI configuration file. + /// + public class IniComponent : IIniComponent { + + /// + /// Internal reference for . + /// + protected readonly Dictionary _entries; + + /// + public virtual IReadOnlyDictionary Entries { + get { + _lock.Enter(); + try { + return _entries; // assume cast-based immutability + } finally { + _lock.Exit(); + } + } + } + + /// + /// Internal reference for . + /// + protected readonly List _sections; + + /// + public virtual IReadOnlyList Sections { + get { + _lock.Enter(); + try { + return _sections; // assume cast-based immutability + } finally { + _lock.Exit(); + } + } + } + + /// + /// A lock to provide thread-safe access to the component. + /// + protected readonly Lock _lock; + + /// + /// Constructs a new + /// with the specified entries, sections, and lock. + /// + /// The global entries in the INI file as a dictionary of key-value pairs, or to start with an empty dictionary. + /// The sections in the INI file as a list of instances, or to start with an empty list. + /// An existing lock to use for thread-safety, or to create a new lock. + public IniComponent(IReadOnlyDictionary? entries = null, IReadOnlyList? sections = null, Lock? @lock = null) { + _entries = entries is null ? new() : new(entries); + _sections = sections is null ? new() : new(sections); + _lock = @lock ?? new(); + } + + /// + public void SetValue(string key, string? value) { + if (string.IsNullOrWhiteSpace(key)) throw new ArgumentNullException(nameof(key), "The key cannot be null or whitespace."); + _lock.Enter(); + try { + _entries[key] = value; + } finally { + _lock.Exit(); + } + } + + /// + public bool TryGetValue(string key, [NotNullWhen(true)] out string? value) { + if (string.IsNullOrWhiteSpace(key)) throw new ArgumentNullException(nameof(key), "The key cannot be null or whitespace."); + _lock.Enter(); + try { + return _entries.TryGetValue(key, out value); + } finally { + _lock.Exit(); + } + } + + /// + public bool TryRemoveValue(string key, [NotNullWhen(true)] out string? removed) { + if (string.IsNullOrWhiteSpace(key)) throw new ArgumentNullException(nameof(key), "The key cannot be null or whitespace."); + _lock.Enter(); + try { + return _entries.Remove(key, out removed); + } finally { + _lock.Exit(); + } + } + + /// + public void SetSection(IIniSectionComponent section) { + if (section is null) throw new ArgumentNullException(nameof(section), "The section cannot be null."); + _lock.Enter(); + try { + int index = _sections.FindIndex(s => s.Name.Equals(section.Name, StringComparison.OrdinalIgnoreCase)); + if (index >= 0) { + _sections[index] = section; + } else { + _sections.Add(section); + } + } finally { + _lock.Exit(); + } + } + + /// + public bool TryGetSection(string name, [NotNullWhen(true)] out IIniSectionComponent? section) { + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name), "The section name cannot be null or whitespace."); + _lock.Enter(); + try { + section = _sections.FirstOrDefault(s => s.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); + return section != null; + } finally { + _lock.Exit(); + } + } + + /// + public bool TryRemoveSection(string name, [NotNullWhen(true)] out IIniSectionComponent? removed) { + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name), "The section name cannot be null or whitespace."); + _lock.Enter(); + try { + int index = _sections.FindIndex(s => s.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); + if (index >= 0) { + removed = _sections[index]; + _sections.RemoveAt(index); + return true; + } else { + removed = null; + return false; + } + } finally { + _lock.Exit(); + } + } + + /// + public override string ToString() { + StringBuilder sb = new(); + sb.Append(nameof(IniComponent)); + sb.Append(' ').Append('{').Append(' '); + + sb.Append(nameof(Entries)).Append(' ').Append('=').Append(' '); + sb.Append('{').Append(' '); + bool first = true; + foreach (KeyValuePair kvp in Entries) { + if (!first) sb.Append(',').Append(' '); + first = false; + sb.Append(kvp.Key); + sb.Append(' ').Append('=').Append(' '); + sb.Append(kvp.Value ?? ""); + } + sb.Append(' ').Append('}'); + + sb.Append(',').Append(' '); + sb.Append(nameof(Sections)); + sb.Append(' ').Append('=').Append(' '); + sb.Append('[').Append(' '); + first = true; + foreach (IIniSectionComponent section in Sections) { + if (!first) sb.Append(',').Append(' '); + first = false; + sb.Append(section); + } + sb.Append(' ').Append(']'); + + sb.Append(' ').Append('}'); + return sb.ToString(); + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/Components/IniSectionComponent.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/Components/IniSectionComponent.cs new file mode 100644 index 0000000..7e6953c --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/Components/IniSectionComponent.cs @@ -0,0 +1,147 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Text; +using System.Threading; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Modules.Arcane.Ini { + + /// + /// A mutable component for a section in an INI configuration file. + /// + public class IniSectionComponent : IIniSectionComponent { + + /// + /// Internal reference for . + /// + protected string _name; + + /// + public virtual string Name { + get { + _lock.Enter(); + try { + return _name; + } finally { + _lock.Exit(); + } + } + set { + _lock.Enter(); + try { + if (string.IsNullOrWhiteSpace(value)) throw new ArgumentNullException(nameof(value), "The section name cannot be null or whitespace."); + _name = value; + } finally { + _lock.Exit(); + } + } + } + + /// + /// Internal reference for . + /// + protected readonly Dictionary _entries; + + /// + public virtual IReadOnlyDictionary Entries { + get { + _lock.Enter(); + try { + return _entries; // assume cast-based immutability + } finally { + _lock.Exit(); + } + } + } + + /// + /// A lock to provide thread-safe access to the component. + /// + protected readonly Lock _lock; + + /// + /// Constructs a new + /// with the specified name, entries, and lock. + /// + /// The name of the section. + /// The entries in the section as a dictionary of key-value pairs, or to start with an empty dictionary. + /// An existing lock to use for thread-safety, or to create a new one. + public IniSectionComponent(string name, IReadOnlyDictionary? entries = null, Lock? @lock = null) { + _name = string.IsNullOrWhiteSpace(name) ? throw new ArgumentNullException(nameof(name), "The section name cannot be null or whitespace.") : name; + _entries = entries is null ? new() : new Dictionary(entries); + _lock = @lock ?? new Lock(); + } + + /// + public virtual void SetValue(string key, string? value) { + if (string.IsNullOrWhiteSpace(key)) throw new ArgumentNullException(nameof(key), "The key cannot be null or whitespace."); + _lock.Enter(); + try { + _entries[key] = value; + } finally { + _lock.Exit(); + } + } + + /// + public virtual bool TryGetValue(string key, [NotNullWhen(true)] out string? value) { + if (string.IsNullOrWhiteSpace(key)) throw new ArgumentNullException(nameof(key), "The key cannot be null or whitespace."); + _lock.Enter(); + try { + return _entries.TryGetValue(key, out value); + } finally { + _lock.Exit(); + } + } + + /// + public virtual bool TryRemoveValue(string key, [NotNullWhen(true)] out string? removed) { + if (string.IsNullOrWhiteSpace(key)) throw new ArgumentNullException(nameof(key), "The key cannot be null or whitespace."); + _lock.Enter(); + try { + return _entries.Remove(key, out removed); + } finally { + _lock.Exit(); + } + } + + /// + public override string ToString() { + StringBuilder sb = new(); + sb.Append(nameof(IniSectionComponent)); + sb.Append(' ').Append('{').Append(' '); + + sb.Append(nameof(Name)).Append(' ').Append('=').Append(' ').Append(Name); + sb.Append(',').Append(' '); + + sb.Append(nameof(Entries)).Append(' ').Append('=').Append(' '); + sb.Append('{').Append(' '); + bool first = true; + foreach (KeyValuePair kvp in Entries) { + if (!first) sb.Append(',').Append(' '); + first = false; + sb.Append(kvp.Key); + sb.Append(' ').Append('=').Append(' '); + sb.Append(kvp.Value ?? ""); + } + sb.Append(' ').Append('}'); + + sb.Append(' ').Append('}'); + return sb.ToString(); + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/Extensions/CatalystAppBuilderExtensions.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/Extensions/CatalystAppBuilderExtensions.cs new file mode 100644 index 0000000..2db25dc --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/Extensions/CatalystAppBuilderExtensions.cs @@ -0,0 +1,53 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Modules.Arcane.Ini; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Builders.Extensions { + + /// + /// Builder extensions for the . + /// + public static class CatalystAppBuilderExtensions { + + /// + /// Adds the Arcane INI model to the . + /// + /// + /// + /// The Arcane INI model adds the following to your CatalystUI application: + /// + /// + /// + /// + /// + /// + /// Click on any of the above links to learn more about each component. + /// + /// + /// The to add the model to. + /// The with the Arcane INI model added. + public static CatalystAppBuilder AddArcaneIniModule(this CatalystAppBuilder builder) { + IniDataConnector iniDataConnector = new(); + IniComponentsLayer iniComponentsLayer = new(); + IniParserConnector iniParserConnector = new(); + IniSemanticsLayer iniSemanticsLayer = new(); + ModelRegistry.RegisterConnector(iniDataConnector); + ModelRegistry.RegisterLayer(iniComponentsLayer); + ModelRegistry.RegisterConnector(iniParserConnector); + ModelRegistry.RegisterLayer(iniSemanticsLayer); + return builder; + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/IniComponentsLayer.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/IniComponentsLayer.cs new file mode 100644 index 0000000..f6ea756 --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/IniComponentsLayer.cs @@ -0,0 +1,33 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace Catalyst.Modules.Arcane.Ini { + + /// + /// The Arcane implementation of the CatalystUI type. + /// + public sealed class IniComponentsLayer : IIniComponentsLayer { + + /// + public IIniComponent CreateComponent(IReadOnlyDictionary? entries = null, IReadOnlyList? sections = null) { + return new IniComponent(entries, sections); + } + + /// + public IIniSectionComponent CreateSectionComponent(string name, IReadOnlyDictionary? entries = null) { + return new IniSectionComponent(name, entries); + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/IniDataConnector.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/IniDataConnector.cs new file mode 100644 index 0000000..55e38ff --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/IniDataConnector.cs @@ -0,0 +1,54 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using Catalyst.Supplementary; +using System; +using System.Diagnostics.CodeAnalysis; +using System.IO; + +namespace Catalyst.Modules.Arcane.Ini { + + /// + /// The Arcane implementation of the CatalystUI type. + /// + public class IniDataConnector : FileDataConnectorBase, IIniDataConnector { + + /// + public override bool TryRead(FileInfo fileInfo, [NotNullWhen(true)] out IIniSemantic? semantic) { + BindingContract? contract = null; + if (!_boundStreams.TryGetValue(fileInfo, out FileStream? stream)) { + if (!TryBind(fileInfo, out contract)) throw new NotSupportedException($"The data connector failed to bind to the file '{fileInfo.FullName}'."); + if (!_boundStreams.TryGetValue(fileInfo, out stream)) throw new FileNotFoundException($"The file '{fileInfo.FullName}' could not be found or opened."); + } + try { + throw new NotImplementedException(); + } finally { + contract?.Dispose(); + } + } + + /// + public override bool TryWrite(FileInfo fileInfo, IIniSemantic semantic) { + BindingContract? contract = null; + if (!_boundStreams.TryGetValue(fileInfo, out FileStream? stream)) { + if (!TryBind(fileInfo, out contract)) throw new NotSupportedException($"The data connector failed to bind to the file '{fileInfo.FullName}'."); + if (!_boundStreams.TryGetValue(fileInfo, out stream)) throw new FileNotFoundException($"The file '{fileInfo.FullName}' could not be found or opened."); + } + try { + throw new NotImplementedException(); + } finally { + contract?.Dispose(); + } + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/IniParserConnector.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/IniParserConnector.cs new file mode 100644 index 0000000..acc15ee --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/IniParserConnector.cs @@ -0,0 +1,55 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Threading; + +namespace Catalyst.Modules.Arcane.Ini { + + /// + /// The Arcane implementation of the CatalystUI type. + /// + public sealed class IniParserConnector : IIniParserConnector { + + /// + public IIniComponent ToComponent(IIniSemantic semantic) { + // Create a lock for the converted component. + Lock @lock = new(); + + // Convert the semantic sections to component sections. + IReadOnlyList semanticSections = semantic.Sections; + IIniSectionComponent[] componentSections = new IIniSectionComponent[semanticSections.Count]; + for (int i = 0; i < semanticSections.Count; i++) { + IIniSectionSemantic semanticSection = semanticSections[i]; + componentSections[i] = new IniSectionComponent(semanticSection.Name, semanticSection.Entries, @lock); + } + + // Convert the semantic to a component. + return new IniComponent(semantic.Entries, componentSections, @lock); + } + + /// + public IIniSemantic ToSemantic(IIniComponent component) { + // Convert the component sections to semantic sections. + IReadOnlyList componentSections = component.Sections; + IIniSectionSemantic[] semanticSections = new IIniSectionSemantic[componentSections.Count]; + for (int i = 0; i < componentSections.Count; i++) { + IIniSectionComponent componentSection = componentSections[i]; + semanticSections[i] = new IniSectionSemantic(componentSection.Name, componentSection.Entries); + } + + // Convert the component to a semantic. + return new IniSemantic(component.Entries, semanticSections); + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/IniSemanticsLayer.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/IniSemanticsLayer.cs new file mode 100644 index 0000000..7326210 --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/IniSemanticsLayer.cs @@ -0,0 +1,33 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace Catalyst.Modules.Arcane.Ini { + + /// + /// The Arcane implementation of the CatalystUI type. + /// + public sealed class IniSemanticsLayer : IIniSemanticsLayer { + + /// + public IIniSemantic CreateSemantic(IReadOnlyDictionary entries, IReadOnlyList sections) { + return new IniSemantic(entries, sections); + } + + /// + public IIniSectionSemantic CreateSectionSemantic(string name, IReadOnlyDictionary entries) { + return new IniSectionSemantic(name, entries); + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/Semantics/IniSectionSemantic.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/Semantics/IniSectionSemantic.cs new file mode 100644 index 0000000..8a93f4e --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/Semantics/IniSectionSemantic.cs @@ -0,0 +1,77 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Text; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Modules.Arcane.Ini { + + /// + /// An immutable semantic for a section in an INI configuration file. + /// + public record IniSectionSemantic : IIniSectionSemantic { + + /// + public required string Name { get; init; } + + /// + public required IReadOnlyDictionary Entries { get; init; } + + /// + /// Constructs a new + /// with the specified name and entries. + /// + /// The name of the section. + /// The entries in the section as a dictionary of key-value pairs. + /// Thrown if is null or whitespace, or if is null. + [SetsRequiredMembers] + public IniSectionSemantic(string name, IReadOnlyDictionary entries) { + Name = string.IsNullOrWhiteSpace(name) ? throw new ArgumentNullException(nameof(name), "The section name cannot be null or whitespace.") : name; + Entries = entries ?? throw new ArgumentNullException(nameof(entries), "The entries dictionary cannot be null."); + } + + /// + public virtual bool TryGetValue(string key, [NotNullWhen(true)] out string? value) { + if (string.IsNullOrWhiteSpace(key)) throw new ArgumentNullException(nameof(key), "The key cannot be null or whitespace."); + return Entries.TryGetValue(key, out value); + } + + /// + public override string ToString() { + StringBuilder sb = new(); + sb.Append(nameof(IniSectionSemantic)); + sb.Append(' ').Append('{').Append(' '); + + sb.Append(nameof(Name)).Append(' ').Append('=').Append(' ').Append(Name); + sb.Append(',').Append(' '); + + sb.Append(nameof(Entries)).Append(' ').Append('=').Append(' '); + sb.Append('{').Append(' '); + bool first = true; + foreach (KeyValuePair kvp in Entries) { + if (!first) sb.Append(',').Append(' '); + first = false; + sb.Append(kvp.Key); + sb.Append(' ').Append('=').Append(' '); + sb.Append(kvp.Value ?? ""); + } + sb.Append(' ').Append('}'); + + sb.Append(' ').Append('}'); + return sb.ToString(); + } + + } + +} \ No newline at end of file diff --git a/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/Semantics/IniSemantic.cs b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/Semantics/IniSemantic.cs new file mode 100644 index 0000000..0a3aca8 --- /dev/null +++ b/CatalystUI/Modules/Arcane/CatalystUI.Modules.Arcane.Ini/Semantics/IniSemantic.cs @@ -0,0 +1,94 @@ +// ------------------------------------------------------------------------------------------------- +// CatalystUI Framework for .NET Core - https://catalystui.org/ +// Copyright (c) 2025 CatalystUI LLC. All rights reserved. +// +// This file is part of CatalystUI and is provided as part of an early-access release. +// Unauthorized commercial use, distribution, or modification is strictly prohibited. +// +// This software is not open source and is not publicly licensed. +// For full terms, see the LICENSE and NOTICE files in the project root. +// ------------------------------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; + +// ReSharper disable once CheckNamespace +namespace Catalyst.Modules.Arcane.Ini { + + /// + /// An immutable semantic for an INI configuration file. + /// + public record IniSemantic : IIniSemantic { + + /// + public required IReadOnlyDictionary Entries { get; init; } + + /// + public required IReadOnlyList Sections { get; init; } + + /// + /// Constructs a new + /// with the specified entries and sections. + /// + /// The global entries in the INI file as a dictionary of key-value pairs. + /// The sections in the INI file as a list of instances. + /// Thrown if or is null. + [SetsRequiredMembers] + public IniSemantic(IReadOnlyDictionary entries, IReadOnlyList sections) { + Entries = entries ?? throw new ArgumentNullException(nameof(entries), "The entries dictionary cannot be null."); + Sections = sections ?? throw new ArgumentNullException(nameof(sections), "The sections list cannot be null."); + } + + /// + public virtual bool TryGetValue(string key, [NotNullWhen(true)] out string? value) { + if (string.IsNullOrWhiteSpace(key)) throw new ArgumentNullException(nameof(key), "The key cannot be null or whitespace."); + return Entries.TryGetValue(key, out value); + } + + /// + public virtual bool TryGetSection(string name, [NotNullWhen(true)] out IIniSectionSemantic? section) { + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name), "The section name cannot be null or whitespace."); + section = Sections.FirstOrDefault(s => s.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); + return section != null; + } + + /// + public override string ToString() { + StringBuilder sb = new(); + sb.Append(nameof(IniSemantic)); + sb.Append(' ').Append('{').Append(' '); + + sb.Append(nameof(Entries)).Append(' ').Append('=').Append(' '); + sb.Append('{').Append(' '); + bool first = true; + foreach (KeyValuePair kvp in Entries) { + if (!first) sb.Append(',').Append(' '); + first = false; + sb.Append(kvp.Key); + sb.Append(' ').Append('=').Append(' '); + sb.Append(kvp.Value ?? ""); + } + sb.Append(' ').Append('}'); + + sb.Append(',').Append(' '); + sb.Append(nameof(Sections)); + sb.Append(' ').Append('=').Append(' '); + sb.Append('[').Append(' '); + first = true; + foreach (IIniSectionSemantic section in Sections) { + if (!first) sb.Append(',').Append(' '); + first = false; + sb.Append(section); + } + sb.Append(' ').Append(']'); + + sb.Append(' ').Append('}'); + return sb.ToString(); + } + + } + +} \ No newline at end of file From 4b813ce529e53d72b742a597a5defd92f3b56fb5 Mon Sep 17 00:00:00 2001 From: FireController#1847 Date: Sun, 26 Oct 2025 14:58:24 -0600 Subject: [PATCH 20/20] Add manual .NET action trigger --- .github/workflows/dotnet-manual.yml | 52 +++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 .github/workflows/dotnet-manual.yml diff --git a/.github/workflows/dotnet-manual.yml b/.github/workflows/dotnet-manual.yml new file mode 100644 index 0000000..abe8f29 --- /dev/null +++ b/.github/workflows/dotnet-manual.yml @@ -0,0 +1,52 @@ +# This workflow will build a .NET project +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net + +name: .NET (Manual Build) +permissions: + contents: read + +on: + workflow_dispatch: # Manual trigger only + inputs: + branch: + description: 'Branch to build' + required: false + default: '' + type: string + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: ${{ github.event.inputs.branch || github.ref_name }} + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 9.0.x + + - name: Install Mono + run: | + sudo apt-get update + sudo apt-get install -y mono-devel + + - name: Run Configure setup + shell: pwsh + run: | + Write-Host "Running Configure.ps1..." + echo "1" | pwsh ./Configure.ps1 setup -Verbose + + - name: Restore dependencies + working-directory: CatalystUI + run: dotnet restore + + - name: Build + working-directory: CatalystUI + run: dotnet build --no-restore + + - name: Test + working-directory: CatalystUI + run: dotnet test --no-build --verbosity normal