diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..fd356df
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,551 @@
+# Version: 4.1.1 (Using https://semver.org/)
+# Updated: 2022-05-23
+# See https://github.com/RehanSaeed/EditorConfig/releases for release notes.
+# See https://github.com/RehanSaeed/EditorConfig for updates to this file.
+# See http://EditorConfig.org for more information about .editorconfig files.
+
+##########################################
+# Common Settings
+##########################################
+
+# This file is the top-most EditorConfig file
+root = true
+
+# All Files
+[*]
+charset = utf-8
+indent_style = tab
+# indent_size = 2
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+##########################################
+# File Extension Settings
+##########################################
+
+# Visual Studio Solution Files
+[*.sln]
+indent_style = tab
+
+# Visual Studio XML Project Files
+[*.{csproj,vbproj,vcxproj.filters,proj,projitems,shproj}]
+indent_size = 2
+
+# XML Configuration Files
+[*.{xml,config,props,targets,nuspec,resx,ruleset,vsixmanifest,vsct}]
+indent_size = 2
+
+# JSON Files
+[*.{json,json5,webmanifest}]
+indent_size = 2
+
+# YAML Files
+[*.{yml,yaml}]
+indent_size = 2
+
+# Markdown Files
+[*.{md,mdx}]
+trim_trailing_whitespace = false
+
+# Web Files
+[*.{htm,html,js,jsm,ts,tsx,cjs,cts,ctsx,mjs,mts,mtsx,css,sass,scss,less,pcss,svg,vue}]
+indent_size = 2
+
+# Batch Files
+[*.{cmd,bat}]
+end_of_line = crlf
+
+# Bash Files
+[*.sh]
+end_of_line = lf
+
+# Makefiles
+[Makefile]
+indent_style = tab
+
+##########################################
+# Default .NET Code Style Severities
+# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/configuration-options#scope
+##########################################
+
+[*.{cs,csx,cake,vb,vbx}]
+# Default Severity for all .NET Code Style rules below
+dotnet_analyzer_diagnostic.severity = none
+
+##########################################
+# Language Rules
+# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/language-rules
+##########################################
+
+# .NET Style Rules
+# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/language-rules#net-style-rules
+[*.{cs,csx,cake,vb,vbx}]
+indent_style = tab
+
+# "this." and "Me." qualifiers
+dotnet_style_qualification_for_field = false:none
+dotnet_style_qualification_for_property = false:none
+dotnet_style_qualification_for_method = false:none
+dotnet_style_qualification_for_event = false:none
+# Language keywords instead of framework type names for type references
+dotnet_style_predefined_type_for_locals_parameters_members = true:none
+dotnet_style_predefined_type_for_member_access = true:none
+# Modifier preferences
+dotnet_style_require_accessibility_modifiers = always:none
+csharp_preferred_modifier_order = new,static,private,virtual,public,protected,internal,extern,abstract,sealed,override,readonly,unsafe,volatile,async:none
+visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:none
+dotnet_style_readonly_field = true:none
+# Parentheses preferences
+dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:none
+dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:none
+dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:none
+dotnet_style_parentheses_in_other_operators = never_if_unnecessary:none
+# Expression-level preferences
+dotnet_style_object_initializer = true:none
+dotnet_style_collection_initializer = true:none
+dotnet_style_explicit_tuple_names = true:none
+dotnet_style_prefer_inferred_tuple_names = true:none
+dotnet_style_prefer_inferred_anonymous_type_member_names = true:none
+dotnet_style_prefer_auto_properties = true:none
+dotnet_style_prefer_conditional_expression_over_assignment = false:suggestion
+dotnet_diagnostic.IDE0045.severity = suggestion
+dotnet_style_prefer_conditional_expression_over_return = false:suggestion
+dotnet_diagnostic.IDE0046.severity = suggestion
+dotnet_style_prefer_compound_assignment = true:none
+dotnet_style_prefer_simplified_interpolation = true:none
+dotnet_style_prefer_simplified_boolean_expressions = true:none
+# Null-checking preferences
+dotnet_style_coalesce_expression = true:none
+dotnet_style_null_propagation = true:none
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:none
+# File header preferences
+# file_header_template = \n© PROJECT-AUTHOR\n
+# If you use StyleCop, you'll need to disable SA1636: File header copyright text should match.
+# dotnet_diagnostic.SA1636.severity = none
+# Undocumented
+dotnet_style_operator_placement_when_wrapping = end_of_line:none
+csharp_style_prefer_null_check_over_type_check = true:none
+
+# C# Style Rules
+# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/language-rules#c-style-rules
+[*.{cs,csx,cake}]
+# 'var' preferences
+csharp_style_var_for_built_in_types = true:none
+csharp_style_var_when_type_is_apparent = true:none
+csharp_style_var_elsewhere = false
+# Expression-bodied members
+csharp_style_expression_bodied_methods = true:none
+csharp_style_expression_bodied_constructors = true:none
+csharp_style_expression_bodied_operators = true:none
+csharp_style_expression_bodied_properties = true:none
+csharp_style_expression_bodied_indexers = true:none
+csharp_style_expression_bodied_accessors = true:none
+csharp_style_expression_bodied_lambdas = true:none
+csharp_style_expression_bodied_local_functions = true:none
+# Pattern matching preferences
+csharp_style_pattern_matching_over_is_with_cast_check = true:none
+csharp_style_pattern_matching_over_as_with_null_check = true:none
+csharp_style_prefer_switch_expression = true:none
+csharp_style_prefer_pattern_matching = true:none
+csharp_style_prefer_not_pattern = true:none
+# Expression-level preferences
+csharp_style_inlined_variable_declaration = true:none
+csharp_prefer_simple_default_expression = true:none
+csharp_style_pattern_local_over_anonymous_function = true:none
+csharp_style_deconstructed_variable_declaration = true:none
+csharp_style_prefer_index_operator = true:none
+csharp_style_prefer_range_operator = true:none
+csharp_style_implicit_object_creation_when_type_is_apparent = true:none
+# "Null" checking preferences
+csharp_style_throw_expression = true:none
+csharp_style_conditional_delegate_call = true:none
+# Code block preferences
+csharp_prefer_braces = true:none
+csharp_prefer_simple_using_statement = true:suggestion
+dotnet_diagnostic.IDE0063.severity = suggestion
+# 'using' directive preferences
+# csharp_using_directive_placement = inside_namespace:none
+# Modifier preferences
+csharp_prefer_static_local_function = true:none
+
+##########################################
+# Unnecessary Code Rules
+# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/unnecessary-code-rules
+##########################################
+
+# .NET Unnecessary code rules
+[*.{cs,csx,cake,vb,vbx}]
+dotnet_code_quality_unused_parameters = all:none
+dotnet_remove_unnecessary_suppression_exclusions = none:none
+
+# C# Unnecessary code rules
+[*.{cs,csx,cake}]
+csharp_style_unused_value_expression_statement_preference = discard_variable:suggestion
+dotnet_diagnostic.IDE0058.severity = suggestion
+csharp_style_unused_value_assignment_preference = discard_variable:suggestion
+dotnet_diagnostic.IDE0059.severity = suggestion
+
+##########################################
+# Formatting Rules
+# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/formatting-rules
+##########################################
+
+# .NET formatting rules
+# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#net-formatting-rules
+[*.{cs,csx,cake,vb,vbx}]
+# Organize using directives
+dotnet_sort_system_directives_first = false
+dotnet_separate_import_directive_groups = false
+# Dotnet namespace options
+dotnet_style_namespace_match_folder = true:suggestion
+dotnet_diagnostic.IDE0130.severity = suggestion
+
+# C# formatting rules
+# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#c-formatting-rules
+[*.{cs,csx,cake}]
+# Newline options
+# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#new-line-options
+csharp_new_line_before_open_brace = all
+csharp_new_line_before_else = true
+csharp_new_line_before_catch = true
+csharp_new_line_before_finally = true
+csharp_new_line_before_members_in_object_initializers = true
+csharp_new_line_before_members_in_anonymous_types = true
+csharp_new_line_between_query_expression_clauses = true
+# Indentation options
+# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#indentation-options
+csharp_indent_case_contents = true
+csharp_indent_switch_labels = true
+csharp_indent_labels = no_change
+csharp_indent_block_contents = true
+csharp_indent_braces = false
+csharp_indent_case_contents_when_block = false
+# Spacing options
+# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#spacing-options
+csharp_space_after_cast = false
+csharp_space_after_keywords_in_control_flow_statements = false
+csharp_space_between_parentheses = control_flow_statements, expressions
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_around_binary_operators = no_change
+csharp_space_between_method_declaration_parameter_list_parentheses = true
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+csharp_space_between_method_declaration_name_and_open_parenthesis = false
+csharp_space_between_method_call_parameter_list_parentheses = true
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+csharp_space_after_comma = true
+csharp_space_before_comma = false
+csharp_space_after_dot = false
+csharp_space_before_dot = false
+csharp_space_after_semicolon_in_for_statement = true
+csharp_space_before_semicolon_in_for_statement = false
+csharp_space_around_declaration_statements = false
+csharp_space_before_open_square_brackets = false
+csharp_space_between_empty_square_brackets = false
+csharp_space_between_square_brackets = false
+# Wrap options
+# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#wrap-options
+csharp_preserve_single_line_statements = false
+csharp_preserve_single_line_blocks = true
+# Namespace options
+# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#namespace-options
+csharp_style_namespace_declarations = file_scoped:none
+
+##########################################
+# .NET Naming Rules
+# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/naming-rules
+##########################################
+
+[*.{cs,csx,cake,vb,vbx}]
+
+##########################################
+# Styles
+##########################################
+
+# camel_case_style - Define the camelCase style
+dotnet_naming_style.camel_case_style.capitalization = camel_case
+# pascal_case_style - Define the PascalCase style
+dotnet_naming_style.pascal_case_style.capitalization = pascal_case
+# first_upper_style - The first character must start with an upper-case character
+dotnet_naming_style.first_upper_style.capitalization = first_word_upper
+# prefix_interface_with_i_style - Interfaces must be PascalCase and the first character of an interface must be an 'I'
+# dotnet_naming_style.prefix_interface_with_i_style.capitalization = pascal_case
+# dotnet_naming_style.prefix_interface_with_i_style.required_prefix = I
+# prefix_type_parameters_with_t_style - Generic Type Parameters must be PascalCase and the first character must be a 'T'
+dotnet_naming_style.prefix_type_parameters_with_t_style.capitalization = pascal_case
+dotnet_naming_style.prefix_type_parameters_with_t_style.required_prefix = T
+# disallowed_style - Anything that has this style applied is marked as disallowed
+dotnet_naming_style.disallowed_style.capitalization = pascal_case
+dotnet_naming_style.disallowed_style.required_prefix = ____RULE_VIOLATION____
+dotnet_naming_style.disallowed_style.required_suffix = ____RULE_VIOLATION____
+# internal_error_style - This style should never occur... if it does, it indicates a bug in file or in the parser using the file
+dotnet_naming_style.internal_error_style.capitalization = pascal_case
+dotnet_naming_style.internal_error_style.required_prefix = ____INTERNAL_ERROR____
+dotnet_naming_style.internal_error_style.required_suffix = ____INTERNAL_ERROR____
+
+
+##########################################
+# .NET Design Guideline Field Naming Rules
+# Naming rules for fields follow the .NET Framework design guidelines
+# https://docs.microsoft.com/dotnet/standard/design-guidelines/index
+##########################################
+
+# All public/protected/protected_internal constant fields must be PascalCase
+# https://docs.microsoft.com/dotnet/standard/design-guidelines/field
+dotnet_naming_symbols.public_protected_constant_fields_group.applicable_accessibilities = public, protected, protected_internal
+dotnet_naming_symbols.public_protected_constant_fields_group.required_modifiers = const
+dotnet_naming_symbols.public_protected_constant_fields_group.applicable_kinds = field
+dotnet_naming_rule.public_protected_constant_fields_must_be_pascal_case_rule.symbols = public_protected_constant_fields_group
+dotnet_naming_rule.public_protected_constant_fields_must_be_pascal_case_rule.style = pascal_case_style
+dotnet_naming_rule.public_protected_constant_fields_must_be_pascal_case_rule.severity = none
+
+# All public/protected/protected_internal static readonly fields must be PascalCase
+# https://docs.microsoft.com/dotnet/standard/design-guidelines/field
+dotnet_naming_symbols.public_protected_static_readonly_fields_group.applicable_accessibilities = public, protected, protected_internal
+dotnet_naming_symbols.public_protected_static_readonly_fields_group.required_modifiers = static, readonly
+dotnet_naming_symbols.public_protected_static_readonly_fields_group.applicable_kinds = field
+dotnet_naming_rule.public_protected_static_readonly_fields_must_be_pascal_case_rule.symbols = public_protected_static_readonly_fields_group
+dotnet_naming_rule.public_protected_static_readonly_fields_must_be_pascal_case_rule.style = pascal_case_style
+dotnet_naming_rule.public_protected_static_readonly_fields_must_be_pascal_case_rule.severity = none
+
+# No other public/protected/protected_internal fields are allowed
+# https://docs.microsoft.com/dotnet/standard/design-guidelines/field
+#dotnet_naming_symbols.other_public_protected_fields_group.applicable_accessibilities = public, protected, protected_internal
+#dotnet_naming_symbols.other_public_protected_fields_group.applicable_kinds = field
+#dotnet_naming_rule.other_public_protected_fields_disallowed_rule.symbols = other_public_protected_fields_group
+#dotnet_naming_rule.other_public_protected_fields_disallowed_rule.style = disallowed_style
+#dotnet_naming_rule.other_public_protected_fields_disallowed_rule.severity = error
+
+##########################################
+# StyleCop Field Naming Rules
+# Naming rules for fields follow the StyleCop analyzers
+# This does not override any rules using disallowed_style above
+# https://github.com/DotNetAnalyzers/StyleCopAnalyzers
+##########################################
+
+# All constant fields must be PascalCase
+# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1303.md
+dotnet_naming_symbols.stylecop_constant_fields_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected, private
+dotnet_naming_symbols.stylecop_constant_fields_group.required_modifiers = const
+dotnet_naming_symbols.stylecop_constant_fields_group.applicable_kinds = field
+dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.symbols = stylecop_constant_fields_group
+dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.style = prefix_underscore
+dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.severity = none
+
+# All static readonly fields must be PascalCase
+# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1311.md
+dotnet_naming_symbols.stylecop_static_readonly_fields_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected, private
+dotnet_naming_symbols.stylecop_static_readonly_fields_group.required_modifiers = static, readonly
+dotnet_naming_symbols.stylecop_static_readonly_fields_group.applicable_kinds = field
+dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.symbols = stylecop_static_readonly_fields_group
+dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.style = prefix_underscore
+dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.severity = none
+
+# No non-private instance fields are allowed
+# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1401.md
+dotnet_naming_symbols.stylecop_fields_must_be_private_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected
+dotnet_naming_symbols.stylecop_fields_must_be_private_group.applicable_kinds = field
+dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.symbols = stylecop_fields_must_be_private_group
+dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.style = disallowed_style
+dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.severity = error
+
+dotnet_naming_symbols.stylecop_all_fields_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected, private
+#dotnet_naming_symbols.stylecop_all_fields_group.applicable_accessibilities = *
+dotnet_naming_symbols.stylecop_all_fields_group.applicable_kinds = field
+dotnet_naming_rule.stylecop_private_fields_must_be_camel_case_rule.symbols = stylecop_all_fields_group
+dotnet_naming_rule.stylecop_private_fields_must_be_camel_case_rule.style = prefix_underscore
+dotnet_naming_rule.stylecop_private_fields_must_be_camel_case_rule.severity = none
+
+dotnet_naming_style.prefix_underscore.capitalization = camel_case
+dotnet_naming_style.prefix_underscore.required_prefix = _
+
+
+# Private fields must be camelCase
+# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1306.md
+
+dotnet_naming_symbols.stylecop_static_fields_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected, private
+dotnet_naming_symbols.stylecop_static_fields_group.required_modifiers = static
+dotnet_naming_symbols.stylecop_static_fields_group.applicable_kinds = field
+dotnet_naming_rule.stylecop_private_fields_must_be_camel_case_rule.symbols = stylecop_static_fields_group
+dotnet_naming_rule.stylecop_private_fields_must_be_camel_case_rule.style = prefix_s_underscore
+dotnet_naming_rule.stylecop_private_fields_must_be_camel_case_rule.severity = none
+
+
+dotnet_naming_style.prefix_s_underscore.capitalization = camel_case
+dotnet_naming_style.prefix_s_underscore.required_prefix = s_
+
+
+# Local variables must be camelCase
+# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1312.md
+dotnet_naming_symbols.stylecop_local_fields_group.applicable_accessibilities = local
+dotnet_naming_symbols.stylecop_local_fields_group.applicable_kinds = local
+dotnet_naming_rule.stylecop_local_fields_must_be_camel_case_rule.symbols = stylecop_local_fields_group
+dotnet_naming_rule.stylecop_local_fields_must_be_camel_case_rule.style = camel_case_style
+dotnet_naming_rule.stylecop_local_fields_must_be_camel_case_rule.severity = silent
+
+
+d
+
+
+# This rule should never fire. However, it's included for at least two purposes:
+# First, it helps to understand, reason about, and root-case certain types of issues, such as bugs in .editorconfig parsers.
+# Second, it helps to raise immediate awareness if a new field type is added (as occurred recently in C#).
+#dotnet_naming_symbols.sanity_check_uncovered_field_case_group.applicable_accessibilities = *
+#dotnet_naming_symbols.sanity_check_uncovered_field_case_group.applicable_kinds = field
+#dotnet_naming_rule.sanity_check_uncovered_field_case_rule.symbols = sanity_check_uncovered_field_case_group
+#dotnet_naming_rule.sanity_check_uncovered_field_case_rule.style = internal_error_style
+#dotnet_naming_rule.sanity_check_uncovered_field_case_rule.severity = error
+
+
+##########################################
+# Other Naming Rules
+##########################################
+
+# All of the following must be PascalCase:
+# - Namespaces
+# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-namespaces
+# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1300.md
+# - Classes and Enumerations
+# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces
+# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1300.md
+# - Delegates
+# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces#names-of-common-types
+# - Constructors, Properties, Events, Methods
+# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-type-members
+dotnet_naming_symbols.element_group.applicable_kinds = namespace, class, enum, struct, delegate, event, method, property
+dotnet_naming_rule.element_rule.symbols = element_group
+dotnet_naming_rule.element_rule.style = pascal_case_style
+dotnet_naming_rule.element_rule.severity = none
+
+# Interfaces use PascalCase and are prefixed with uppercase 'I'
+# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces
+dotnet_naming_symbols.interface_group.applicable_kinds = interface
+dotnet_naming_rule.interface_rule.symbols = interface_group
+dotnet_naming_rule.interface_rule.style = camel_case
+dotnet_naming_rule.interface_rule.severity = none
+
+# Generics Type Parameters use PascalCase and are prefixed with uppercase 'T'
+# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces
+dotnet_naming_symbols.type_parameter_group.applicable_kinds = type_parameter
+dotnet_naming_rule.type_parameter_rule.symbols = type_parameter_group
+dotnet_naming_rule.type_parameter_rule.style = prefix_type_parameters_with_t_style
+dotnet_naming_rule.type_parameter_rule.severity = none
+
+# Function parameters use camelCase
+# https://docs.microsoft.com/dotnet/standard/design-guidelines/naming-parameters
+dotnet_naming_symbols.parameters_group.applicable_kinds = parameter
+dotnet_naming_rule.parameters_rule.symbols = parameters_group
+dotnet_naming_rule.parameters_rule.style = camel_case_style
+dotnet_naming_rule.parameters_rule.severity = none
+
+
+
+##########################################
+# Chickensoft Rule Overrides
+##########################################
+
+# Allow using keywords as names
+# dotnet_diagnostic.CA1716.severity = none
+# Don't require culture info for ToString()
+dotnet_diagnostic.CA1304.severity = none
+# Don't require a string comparison for comparing strings.
+dotnet_diagnostic.CA1310.severity = none
+# Don't require a string format specifier.
+dotnet_diagnostic.CA1305.severity = none
+# Allow protected fields.
+dotnet_diagnostic.CA1051.severity = none
+# Don't warn about checking values that are supposedly never null. Sometimes
+# they are actually null.
+dotnet_diagnostic.CS8073.severity = none
+# Don't remove seemingly "unnecessary" assignments, as they often have
+# intended side-effects.
+dotnet_diagnostic.IDE0059.severity = none
+# Switch/case should always have a default clause. Tell that to Roslynator.
+dotnet_diagnostic.RCS1070.severity = none
+# Tell roslynator not to eat unused parameters.
+dotnet_diagnostic.RCS1163.severity = none
+# Tell dotnet not to remove unused parameters.
+dotnet_diagnostic.IDE0060.severity = none
+# Tell roslynator not to remove `partial` modifiers.
+dotnet_diagnostic.RCS1043.severity = none
+# Tell roslynator not to make classes static so aggressively.
+dotnet_diagnostic.RCS1102.severity = none
+# Roslynator wants to make properties readonly all the time, so stop it.
+# The developer knows best when it comes to contract definitions with Godot.
+dotnet_diagnostic.RCS1170.severity = none
+# Allow expression values to go unused, even without discard variable.
+# Otherwise, using Moq would be way too verbose.
+dotnet_diagnostic.IDE0058.severity = none
+# Don't let roslynator turn every local variable into a const.
+# If we did, we'd have to specify the types of local variables far more often,
+# and this style prefers type inference.
+dotnet_diagnostic.RCS1118.severity = none
+# Enums don't need to declare explicit values. Everyone knows they start at 0.
+dotnet_diagnostic.RCS1161.severity = none
+# Allow unconstrained type parameter to be checked for null.
+dotnet_diagnostic.RCS1165.severity = none
+# Allow keyword-based names so that parameter names like `@event` can be used.
+dotnet_diagnostic.CA1716.severity = none
+# Allow me to use the word Collection if I want.
+dotnet_diagnostic.CA1711.severity = none
+# Not disposing of objects in a test is normal within Godot because of scene tree stuff.
+dotnet_diagnostic.CA1001.severity = none
+# No primary constructors — not supported well by tooling.
+dotnet_diagnostic.IDE0290.severity = none
+# Let me comment where I like
+dotnet_diagnostic.RCS1181.severity = none
+# Let me write dumb if checks, keeps it readable
+dotnet_diagnostic.IDE0046.severity = none
+# Don't make me use expression bodies for methods
+dotnet_diagnostic.IDE0022.severity = none
+# Don't use collection shorhand.
+dotnet_diagnostic.IDE0300.severity = none
+dotnet_diagnostic.IDE0028.severity = none
+dotnet_diagnostic.IDE0305.severity = none
+# Don't make me populate a switch expression redundantly
+dotnet_diagnostic.IDE0072.severity = none
+
+
+
+
+##########################################
+# License
+##########################################
+# The following applies as to the .editorconfig file ONLY, and is
+# included below for reference, per the requirements of the license
+# corresponding to this .editorconfig file.
+# See: https://github.com/RehanSaeed/EditorConfig
+#
+# MIT License
+#
+# Copyright (c) 2017-2019 Muhammad Rehan Saeed
+# Copyright (c) 2019 Henry Gabryjelski
+#
+# Permission is hereby granted, free of charge, to any
+# person obtaining a copy of this software and associated
+# documentation files (the "Software"), to deal in the
+# Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute,
+# sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject
+# to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+##########################################
+
+# CS8981: The type name only contains lower-cased ascii characters. Such names may become reserved for the language.
+dotnet_diagnostic.CS8981.severity = silent
+
+# CS1591: Missing XML comment for publicly visible type or member
+dotnet_diagnostic.CS1591.severity = silent
diff --git a/cfg/Config.cs b/cfg/Config.cs
index 1f83864..7ca7fdc 100644
--- a/cfg/Config.cs
+++ b/cfg/Config.cs
@@ -54,8 +54,8 @@ namespace lib
static public void startup( string filename )
{
- res.Mgr.register( load );
- res.Mgr.registerSub( typeof( ConfigBase ) );
+ res.Mgr.Register( load );
+ res.Mgr.RegisterSub( typeof( ConfigBase ) );
s_cfg = load( filename );
diff --git a/imm/FSM.cs b/imm/FSM.cs
index 0afd7d5..3b5991b 100644
--- a/imm/FSM.cs
+++ b/imm/FSM.cs
@@ -1,103 +1,110 @@
-
+#nullable enable
using System;
using System.Runtime.CompilerServices;
-using Optional;
+using imm; // Ensure this namespace is available
-
-
-namespace imm;
-
-
-
-public record class Context : imm.Recorded, Imm
+///
+/// Base context for an FSM.
+/// MUST inherit from Recorded or Timed in your concrete class.
+///
+/// The concrete Context type.
+public abstract record class FsmContextBase : Recorded
+ where TSelf : FsmContextBase
{
- Meta Imm.Meta => base.Meta;
+ // Required for 'with' expressions.
+ protected FsmContextBase(Recorded original) : base(original) { }
+ protected FsmContextBase() { }
}
-public record class State( CTX Context ) : imm.Recorded, Imm
- where TSUB : State
- where CTX : Context
+///
+/// Base state for an FSM.
+/// MUST inherit from Recorded or Timed in your concrete class.
+///
+/// The concrete State type.
+/// The concrete Context type (must be based on FsmContextBase).
+public abstract record class FsmStateBase : Recorded
+ where TSelf : FsmStateBase
+ where TCtx : FsmContextBase
{
- Meta Imm.Meta => base.Meta;
+ ///
+ /// Called when entering this state.
+ ///
+ public virtual (TCtx Context, TSelf State) OnEnter(TCtx context, FsmStateBase oldState)
+ {
+ return (context, (TSelf)this);
+ }
- virtual public (CTX, TSUB) onEnter(CTX ctx, State oldState)
- {
- return (ctx, (TSUB)this);
- }
+ ///
+ /// Called when exiting this state.
+ ///
+ public virtual (TCtx Context, TSelf State) OnExit(TCtx context, FsmStateBase newState)
+ {
+ return (context, (TSelf)this);
+ }
- virtual public (CTX, TSUB) onExit(CTX ctx, State newState)
- {
- return (ctx, (TSUB)this);
- }
+ // Required for 'with' expressions.
+ protected FsmStateBase(Recorded original) : base(original) { }
+ protected FsmStateBase() { }
}
-
-
-public record class FSM : imm.Recorded, Imm
- where TSUB : FSM
- where ST : State
- where CTX : Context
+///
+/// An immutable FSM base class.
+/// MUST inherit from Recorded or Timed in your concrete class.
+///
+/// The concrete FSM type.
+/// The concrete State type.
+/// The concrete Context type.
+public abstract record class FsmBase : Recorded
+ where TSelf : FsmBase
+ where TState : FsmStateBase
+ where TCtx : FsmContextBase
{
- Meta Imm.Meta => base.Meta;
+ public TCtx Context { get; init; }
+ public TState State { get; init; }
- public CTX Context { get; init; }
- public ST State { get; init; }
+ protected FsmBase(TCtx initialContext, TState initialState)
+ {
+ Context = initialContext;
+ State = initialState;
+ }
- public FSM( CTX context, ST stStart )
- {
- Context = context;
- State = stStart;
- }
+ // Required for 'with' expressions.
+ protected FsmBase(Recorded original) : base(original)
+ {
+ var o = original as FsmBase;
+ Context = o!.Context;
+ State = o!.State;
+ }
- public TSUB Transition(ST newState, string reason,
- [CallerMemberName] string memberName = "",
- [CallerFilePath] string filePath = "",
- [CallerLineNumber] int lineNumber = 0,
- [CallerArgumentExpression("newState")]
- string expression = default
- )
- {
- log.debug( $"Trans from {State.GetType().Name} to {newState.GetType().Name} for {reason}" );
+ ///
+ /// Transitions the FSM. It automatically uses the 'Process'
+ /// method appropriate for Recorded or Timed, thanks to virtual overrides.
+ ///
+ public TSelf Transition(
+ TState newState,
+ string reason,
+ [CallerMemberName] string memberName = "",
+ [CallerFilePath] string filePath = "",
+ [CallerLineNumber] int lineNumber = 0,
+ [CallerArgumentExpression("newState")] string expression = "")
+ {
+ Console.WriteLine($"[FSM] Transition: {State.GetType().Name} -> {newState.GetType().Name}. Reason: {reason}");
- var origState = State;
-
- var (newCtx, oldState) = State.onExit(Context, newState);
-
- var (newCTX, storeState) = newState.onEnter(newCtx, oldState);
-
- var newFSM = this.Process( (v) => (this as TSUB) with
- {
- Context = newCTX,
- State = storeState,
- }, $"{reason}" );
-
- return newFSM;
- }
-
- /*
- public TSUB ( Func fn, string reason,
- [CallerMemberName] string member = "",
- [CallerFilePath] string file = "",
- [CallerLineNumber] int line = 0,
- [CallerArgumentExpression("fn")]
- string expression = default
- )
- {
- var newState = fn( State );
-
- if( object.ReferenceEquals( newState, State ) ) return (TSUB)this;
-
- TSUB newFSM = this.Process( this with
- {
- Context = Context,
- State = newState,
- }, $"Processing: {newState.GetType().Name} for {reason}",
- member, file, line, expression );
-
- return (TSUB)newFSM;
- }
- */
-
-}
+ var (ctxAfterExit, stateAfterExit) = State.OnExit(Context, newState);
+ var (ctxAfterEnter, stateAfterEnter) = newState.OnEnter(ctxAfterExit, stateAfterExit);
+ // Since 'this' is at least 'Recorded', we can call the
+ // detailed 'Process'. If 'this' is actually 'Timed', C#'s
+ // virtual dispatch will call the 'Timed' override automatically.
+ return Process(
+ fsm => (TSelf)fsm with
+ {
+ Context = ctxAfterEnter,
+ State = stateAfterEnter
+ },
+ $"Transition to {newState.GetType().Name}: {reason}",
+ memberName, filePath, lineNumber, expression
+ );
+ }
+}
\ No newline at end of file
diff --git a/imm/Imm.cs b/imm/Imm.cs
index 5985c9f..d20a359 100644
--- a/imm/Imm.cs
+++ b/imm/Imm.cs
@@ -1,404 +1,337 @@
-
-
-#nullable enable
-
+#nullable enable
using System;
using System.Collections.Generic;
-using System.Collections.Immutable;
using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
-using System.Linq;
using System.Runtime.CompilerServices;
-using System.Runtime.Serialization;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using lib;
namespace imm;
+///
+/// Represents the base interface for versioned, immutable objects.
+/// Provides access to metadata and potentially the previous version.
+///
+public interface Obj
+{
+ ///
+ /// Gets the base metadata associated with this version.
+ ///
+ Metadata_Versioned Meta { get; }
+
+ ///
+ /// Gets the previous version as a base object, if available.
+ /// Returns null if this is the first version or if history is not tracked.
+ ///
+ Obj? Old { get; }
+
+ ///
+ /// Creates a new version without functional change.
+ /// Returns the new version as an Obj.
+ ///
+ Obj Record(
+ string reason = "Recorded",
+ [CallerMemberName] string memberName = "",
+ [CallerFilePath] string filePath = "",
+ [CallerLineNumber] int lineNumber = 0 );
+}
+
+
+///
+/// Obj delegate for change notifications.
+///
+public delegate void ChangeDelegate( T? oldVersion, T newVersion );
+
+
+///
+/// Represents a generic interface for immutable objects,
+/// providing access to basic processing functions and change notifications.
+///
+public interface Obj : Obj where T : Obj
+{
+ ///
+ /// Gets the change delegate associated with this object.
+ ///
+ ChangeDelegate OnChange { get; set; }
+
+ ///
+ /// Applies a transformation and creates a new version using basic processing.
+ ///
+ T Process(
+ Func fn,
+ string reason = "Processed",
+ [CallerMemberName] string memberName = "",
+ [CallerFilePath] string filePath = "",
+ [CallerLineNumber] int lineNumber = 0 ,
+ [CallerArgumentExpression("fn")] string expStr = "" );
+
+
+ ///
+ /// Creates a new version without a functional change using basic processing.
+ /// Uses 'new' to provide a type-safe return.
+ ///
+ new T Record(
+ string reason = "Recorded",
+ [CallerMemberName] string memberName = "",
+ [CallerFilePath] string filePath = "",
+ [CallerLineNumber] int lineNumber = 0 );
+}
+
+
/*
-T O D O :
-T O D O :
-T O D O :
-x) Add unit tests for all this. This will definitely benefit from them
+static public class ObjExtensions
+{
+ ///
+ /// Creates a new version of the object with the specified reason.
+ ///
+ public static T Record( this T obj, string reason = "Recorded" ) where T : Obj
+ {
+ if( obj is Recorded recorded )
+ {
+ return recorded.Record( reason );
+ }
+ else if( obj is Versioned versioned )
+ {
+ return versioned.Record( reason );
+ }
+ else
+ {
+ // Dont care
+
+ return obj;
+ }
+ }
+}
*/
-static public class Util
+// --- Metadata Hierarchy ---
+
+public interface VersionedMeta
{
- //This can handle both Timed and Recorded
- static public T Process( ref T obj, Func fn, string reason = "",
- [CallerMemberName] string dbgName = "",
- [CallerFilePath] string dbgPath = "",
- [CallerLineNumber] int dbgLine = 0,
- [CallerArgumentExpression("fn")]
- string dbgExp = "" )
- where T : Recorded
- {
- obj = obj.Process( fn, reason, dbgName, dbgPath, dbgLine, dbgExp );
- return obj;
- }
-
- static public T LightProcess( ref T obj, Func fn,
- string reason = "",
- [CallerMemberName] string dbgName = "",
- [CallerFilePath] string dbgPath = "",
- [CallerLineNumber] int dbgLine = 0,
- [CallerArgumentExpression("fn")]
- string dbgExp = ""
- )
- where T : Versioned
- {
- obj = obj.Process( fn, reason );
- return obj;
- }
-}
-
-public interface Meta
-{
- public uint Version => 0;
- public string Reason => "";
- public string Expression => "";
- public string MemberName => "";
- public string FilePath => "";
- public int LineNumber => -1;
- public DateTime CreatedAt => DateTime.MinValue;
- public DateTime TouchedAt => DateTime.MinValue;
-
-}
-
-public interface Imm
-{
- public Meta Meta { get; }
- public object Record( string reason = "",
- [CallerMemberName] string dbgName = "",
- [CallerFilePath] string dbgPath = "",
- [CallerLineNumber] int dbgLine = 0
- );
-
- public Imm Process( Imm next,
- string reason = "",
- [CallerMemberName] string dbgName = "",
- [CallerFilePath] string dbgPath = "",
- [CallerLineNumber] int dbgLine = 0,
- [CallerArgumentExpression("next")]
- string dbgExp = ""
- )
- {
- return next;
- }
-
+ public uint Version { get; }
+ public string Reason { get; }
}
-//[lib.Ser( Types = lib.Types.None )]
-public record class Versioned : Imm
- where T : Versioned
+///
+/// Obj metadata for version tracking.
+///
+public record Metadata_Versioned
{
-
- public delegate void ChangeDelegate( T? old, T next );
-
- public record class MetaData : Meta
- {
- public uint Version { get; internal set; } = 0;
- public string Reason { get; internal set; } = "";
-
- public MetaData() { }
- }
-
- protected Versioned( )
- : this( new MetaData { Version = 1, Reason = $"Versioned.cons" } )
- {
- }
-
- internal Versioned( MetaData meta )
- {
- MetaStorage = meta;
- }
-
- virtual public T Record(
- string reason = "",
- [CallerMemberName] string dbgName = "",
- [CallerFilePath] string dbgPath = "",
- [CallerLineNumber] int dbgLine = 0
- )
- {
- return Process( t => t, reason );
- }
-
-
-
- protected MetaData MetaStorage = new();
-
- [DebuggerBrowsable(DebuggerBrowsableState.Never)]
- public MetaData Meta => MetaStorage;
-
- Meta Imm.Meta => MetaStorage;
-
- [lib.Dont]
- [DebuggerBrowsable(DebuggerBrowsableState.Never)]
- public ChangeDelegate OnChange = (T? old,T cur) => {};
-
- /*
- public void AddOnChange( ChangeDelegate fn,
- string reason,
- [CallerMemberName] string dbgName = "",
- [CallerFilePath] string dbgPath = "",
- [CallerLineNumber] int dbgLine = 0`
- )
- {
- log.debug( $"ADD {log.whatFile(dbgPath)}({dbgLine}): {dbgName} added OnChange bcs {reason}" );
- OnChange += fn;
- }
-
- public void RemOnChange( ChangeDelegate fn,
- [CallerMemberName] string dbgName = "",
- [CallerFilePath] string dbgPath = "",
- [CallerLineNumber] int dbgLine = 0
- )
- {
- log.debug( $"REM {log.whatFile(dbgPath)}({dbgLine}): {dbgName} removing OnChange" );
- OnChange -= fn;
- }
- */
-
-
- public T Process( Func fn, string reason = "" )
- {
- var newT = fn( ( T )this );
-
- return newT with
- {
- MetaStorage = Meta with
- {
- Version = newT.Meta.Version + 1,
- Reason = reason,
- }
- };
- }
-
- object Imm.Record( string reason, string dbgName, string dbgPath, int dbgLine ) => Record( reason, dbgName, dbgPath, dbgLine );
-
- //public object Record( string reason = "", [CallerMemberName] string dbgName = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = 0 ) => Recorded( );
+ public uint Version { get; init; } = 1;
+ public string Reason { get; init; } = "Created";
}
-//[lib.Ser( Types = lib.Types.None )]
-public record class Recorded : Versioned, imm.Imm
- where T : Recorded
+
+public interface RecordedMeta : VersionedMeta
{
-
- new public record class MetaData : Versioned.MetaData
- {
- [lib.Dont]
- public T? Old_backing { get; internal set; }
- public T? Old => Old_backing;
- public string DbgName { get; internal set; } = "";
- public string DbgPath { get; internal set; } = "";
- public int DbgLine { get; internal set; } = -1;
- public string DbgExp { get; internal set; } = "";
-
- public MetaData() { }
- }
-
- public Recorded() : this( new MetaData() )
- {
- }
-
- public Recorded(MetaData meta) : base( meta )
- {
- }
-
- [DebuggerBrowsable(DebuggerBrowsableState.Never)]
- new public MetaData Meta => MetaStorage as MetaData ?? new MetaData();
-
-
- override public T Record(
- string reason = "",
- [CallerMemberName] string dbgName = "",
- [CallerFilePath] string dbgPath = "",
- [CallerLineNumber] int dbgLine = 0
- )
- {
- return Process( t => t, reason, dbgName, dbgPath, dbgLine );
- }
-
- virtual public T Process( T next,
- string reason = "",
- [CallerMemberName] string dbgName = "",
- [CallerFilePath] string dbgPath = "",
- [CallerLineNumber] int dbgLine = 0,
- [CallerArgumentExpression("next")]
- string dbgExp = ""
- )
- {
- return ProcessWork( ( old ) => next, reason, dbgName, dbgPath, dbgLine, dbgExp );
- }
-
-
- virtual public T Process( Func fn,
- string reason = "",
- [CallerMemberName] string dbgName = "",
- [CallerFilePath] string dbgPath = "",
- [CallerLineNumber] int dbgLine = 0,
- [CallerArgumentExpression("fn")]
- string dbgExp = ""
- )
- {
- return ProcessWork( fn, reason, dbgName, dbgPath, dbgLine, dbgExp );
- }
-
- virtual public T ProcessWork( Func fn,
- string reason,
- string dbgName,
- string dbgPath,
- int dbgLine,
- string dbgExp
- )
- {
- var orig = ( T )this;
-
- var next = fn( orig );
-
- if( object.ReferenceEquals( orig, next) )
- return next;
-
- var ret = next with
- {
- //Do the Versioned code here
- MetaStorage = Meta with
- {
- Version = orig.Meta.Version + 1,
- Reason = !string.IsNullOrWhiteSpace( reason ) ? reason : next.Meta.Reason,
-
- Old_backing = orig,
- DbgName = dbgName,
- DbgPath = dbgPath,
- DbgLine = dbgLine,
- DbgExp = dbgExp
- }
- };
-
- OnChange( orig, ret );
-
- return ret;
- }
+ public string MemberName { get; }
+ public string FilePath { get; }
+ public int LineNumber { get; }
+ public string Expression { get; }
}
-public record class Timed : Recorded, imm.Imm
- where T : Timed
+
+///
+/// Metadata for version and recording (debug/caller info, history).
+///
+public record Metadata_Recorded : Metadata_Versioned, RecordedMeta
{
+ internal object? OldObject { get; init; } = null;
+ public string MemberName { get; init; } = "";
+ public string FilePath { get; init; } = "";
+ public int LineNumber { get; init; } = 0;
+ public string Expression { get; init; } = "";
+}
- new public record class MetaData : Recorded.MetaData
- {
- public readonly DateTime CreatedAt = DateTime.Now;
- public DateTime TouchedAt { get; internal set; } = DateTime.Now;
- }
+public interface TimedMeta : RecordedMeta
+{
+ public DateTime CreatedAt { get; }
+ public DateTime TouchedAt { get; }
+}
- public Timed() : this( new MetaData() )
- {
- }
- public Timed( MetaData meta ) : base( meta )
- {
- }
+///
+/// Metadata for version, recording, and timing.
+///
+public record Metadata_Timed : Metadata_Recorded, TimedMeta
+{
+ public DateTime CreatedAt { get; init; } = DateTime.UtcNow;
+ public DateTime TouchedAt { get; init; } = DateTime.UtcNow;
+}
- [DebuggerBrowsable(DebuggerBrowsableState.Never)]
- new public MetaData Meta => MetaStorage as MetaData ?? new MetaData();
+// --- Record Hierarchy ---
- public TimeSpan Since => Meta.TouchedAt - Meta.Old?.Meta.TouchedAt ?? TimeSpan.MaxValue;
+///
+/// Level 1: Basic versioning. Implements Obj.
+///
+public record class Versioned : Obj where T : Versioned
+{
+ public Metadata_Versioned Meta { get; init; } = new();
- public void CallOnChange()
- {
- OnChange( Meta.Old, (T)this );
- }
+ [DebuggerBrowsable( DebuggerBrowsableState.Never )]
+ public ChangeDelegate OnChange { get; set; } = ( o, n ) => { };
- override public T Record(
- string reason = "",
- [CallerMemberName] string dbgName = "",
- [CallerFilePath] string dbgPath = "",
- [CallerLineNumber] int dbgLine = 0
- )
- {
- return Process( t => t with { MetaStorage = t.Meta with { Reason = $"Record {reason}" }}, reason, dbgName, dbgPath, dbgLine );
- }
+ public virtual Obj? Old => null;
- override public T Process( T next,
- string reason = "",
- [CallerMemberName] string dbgName = "",
- [CallerFilePath] string dbgPath = "",
- [CallerLineNumber] int dbgLine = 0,
- [CallerArgumentExpression("next")]
- string dbgExp = ""
- )
- {
- return ProcessWork( ( old ) => next, reason, dbgName, dbgPath, dbgLine, dbgExp );
- }
+ Metadata_Versioned Obj.Meta => this.Meta;
+ Obj? Obj.Old => this.Old;
- public U ProcessFn( Func fn,
- string reason = "",
- [CallerMemberName] string dbgName = "",
- [CallerFilePath] string dbgPath = "",
- [CallerLineNumber] int dbgLine = 0,
- [CallerArgumentExpression("fn")]
- string dbgExp = ""
- )
- where U : T
- {
- return (U)ProcessWork( fn as Func, reason, dbgName, dbgPath, dbgLine, dbgExp );
- }
+ public Versioned() { }
+ protected Versioned( Versioned original )
+ {
+ OnChange = original.OnChange;
+ Meta = original.Meta;
+ }
- override public T Process( Func fn,
- string reason = "",
- [CallerMemberName] string dbgName = "",
- [CallerFilePath] string dbgPath = "",
- [CallerLineNumber] int dbgLine = 0,
- [CallerArgumentExpression("fn")]
- string dbgExp = ""
- )
- => ProcessWork( fn, reason, dbgName, dbgPath, dbgLine, dbgExp );
+ public virtual T Process(
+ Func fn,
+ string reason = "Processed",
+ [CallerMemberName] string memberName = "",
+ [CallerFilePath] string filePath = "",
+ [CallerLineNumber] int lineNumber = 0 ,
+ [CallerArgumentExpression("fn")] string expStr = "" )
+ {
+ var current = (T)this;
+ var next = fn( current );
- override public T ProcessWork( Func fn,
- string reason,
- string dbgName,
- string dbgPath,
- int dbgLine,
- string dbgExp
- )
- {
- var orig = ( T )this;
+ if( ReferenceEquals( current, next ) )
+ return current;
- var next = fn( orig );
+ var newVersion = next with
+ {
+ Meta = new Metadata_Versioned { /*...*/ },
+ OnChange = current.OnChange
+ };
+ newVersion.OnChange( current, newVersion );
+ return newVersion;
+ }
- if( object.ReferenceEquals( orig, next) )
- return next;
+ ///
+ /// Basic Record. Made virtual. Implements Obj.Record.
+ ///
+ public virtual T Record(
+ string reason = "Recorded",
+ [CallerMemberName] string memberName = "",
+ [CallerFilePath] string filePath = "",
+ [CallerLineNumber] int lineNumber = 0 ) => Process( t => t, reason, memberName, filePath, lineNumber );
- var ret = next with
- {
- MetaStorage = Meta with
- {
- //Versioned
- Version = orig.Meta.Version + 1,
- Reason = !string.IsNullOrWhiteSpace( reason ) ? reason : next.Meta.Reason,
-
- //Recorded
- DbgName = dbgName,
- DbgPath = dbgPath,
- DbgLine = dbgLine,
- DbgExp = dbgExp,
- Old_backing = orig,
-
- //Timed
- TouchedAt = DateTime.Now,
- }
-
- };
-
- if( OnChange != null)
- OnChange( orig, ret );
-
- return ret;
- }
+ ///
+ /// Implements Obj.Record by calling the virtual T Record.
+ ///
+ Obj Obj.Record(
+ string reason = "Recorded",
+ [CallerMemberName] string memberName = "",
+ [CallerFilePath] string filePath = "",
+ [CallerLineNumber] int lineNumber = 0 ) => this.Record( reason, memberName, filePath, lineNumber );
}
+
+///
+/// Level 2: Adds history and caller info.
+///
+public record class Recorded : Versioned where T : Recorded
+{
+ new public Metadata_Recorded Meta { get; init; } = new();
+ new public T? Old => Meta.OldObject as T;
+
+ //public override Obj? Old => this.Old;
+ //Metadata_Versioned Obj.Meta => this.Meta;
+
+ public Recorded() { }
+ protected Recorded(Recorded original) : base(original) { Meta = original.Meta; }
+
+ public virtual T Process(
+ Func fn,
+ string reason = "",
+ [CallerMemberName] string memberName = "",
+ [CallerFilePath] string filePath = "",
+ [CallerLineNumber] int lineNumber = 0,
+ [CallerArgumentExpression("fn")] string expStr = "")
+ {
+ var current = (T)this;
+ var next = fn(current);
+
+ if (ReferenceEquals(current, next)) return current;
+
+ var newMeta = new Metadata_Recorded
+ {
+ Version = current.Meta.Version + 1,
+ Reason = reason,
+ MemberName = memberName,
+ FilePath = filePath,
+ LineNumber = lineNumber,
+ Expression = expStr,
+ OldObject = current
+ };
+
+ var newVersion = next with { Meta = newMeta, OnChange = current.OnChange };
+ newVersion.OnChange(current, newVersion);
+ return newVersion;
+ }
+
+ public new T Record(
+ string reason = "Recorded",
+ [CallerMemberName] string memberName = "",
+ [CallerFilePath] string filePath = "",
+ [CallerLineNumber] int lineNumber = 0)
+ {
+ return Process(t => t, reason, memberName, filePath, lineNumber );
+ }
+
+}
+
+///
+/// Level 3: Adds timestamps.
+///
+public record class Timed : Recorded where T : Timed
+{
+ new public Metadata_Timed Meta { get; init; } = new();
+ //Metadata_Versioned Obj.Meta => this.Meta;
+ public TimeSpan SinceLastTouch => Meta.TouchedAt - (Old?.Meta as Metadata_Timed)?.TouchedAt ?? TimeSpan.Zero;
+ public TimeSpan TotalAge => Meta.TouchedAt - Meta.CreatedAt;
+
+ public Timed() { }
+ protected Timed(Timed original) : base(original) { Meta = original.Meta; }
+
+ public override T Process(
+ Func fn,
+ string reason = "",
+ [CallerMemberName] string memberName = "",
+ [CallerFilePath] string filePath = "",
+ [CallerLineNumber] int lineNumber = 0,
+ [CallerArgumentExpression("fn")] string expression = "")
+ {
+ var current = (T)this;
+ var next = fn(current);
+
+ if (ReferenceEquals(current, next)) return current;
+
+ var newMeta = new Metadata_Timed
+ {
+ Version = current.Meta.Version + 1,
+ Reason = reason,
+ MemberName = memberName,
+ FilePath = filePath,
+ LineNumber = lineNumber,
+ Expression = expression,
+ OldObject = current,
+ CreatedAt = DateTime.UtcNow,
+ TouchedAt = DateTime.UtcNow
+ };
+
+ var currentTimedMeta = current.Meta;
+ var newVersion = next with { Meta = newMeta, OnChange = current.OnChange };
+ newVersion.OnChange(current, newVersion);
+ return newVersion;
+ }
+
+ public new T Record(
+ string reason = "Recorded",
+ [CallerMemberName] string memberName = "",
+ [CallerFilePath] string filePath = "",
+ [CallerLineNumber] int lineNumber = 0)
+ {
+ return Process(t => t, reason, memberName, filePath, lineNumber );
+ }
+}
diff --git a/imm/List.cs b/imm/List.cs
index d789711..5407809 100644
--- a/imm/List.cs
+++ b/imm/List.cs
@@ -1,158 +1,77 @@
-using System;
+#nullable enable
+
+using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
+using System.Runtime.CompilerServices;
namespace imm;
+///
+/// An immutable list implementation that tracks history, metadata, and time.
+///
public record class List : Timed>, IImmutableList
{
+ static public List Empty { get; } = new();
- ImmutableList Values = ImmutableList.Empty;
+ public ImmutableList Values { get; init; } = ImmutableList.Empty;
+ public List() { }
+ // Required for 'with' expressions to work with the base class hierarchy
+ protected List(Timed> original) : base(original) { }
+ // Helper to apply changes using the Process method
+ private List Change(
+ Func, ImmutableList> listChange,
+ [CallerMemberName] string memberName = "",
+ [CallerFilePath] string filePath = "",
+ [CallerLineNumber] int lineNumber = 0,
+ [CallerArgumentExpression("listChange")] string reason = "")
+ {
+ var newValues = listChange(Values);
+ return ReferenceEquals(Values, newValues)
+ ? this
+ : Process(l => l with { Values = newValues }, reason, memberName, filePath, lineNumber, reason);
+ }
- public T this[int index] => (( IReadOnlyList )Values)[index];
-
- public int Count => Values.Count;
-
- public List Add( T value )
- {
- return this with { Values = Values.Add( value ) };
- }
-
- public List AddRange( IEnumerable items )
- {
- return this with { Values = Values.AddRange( items ) };
- }
-
- public List Clear()
- {
- return this with { Values = Values.Clear() };
- }
-
- public IEnumerator GetEnumerator()
- {
- return Values.GetEnumerator();
- }
-
- public int IndexOf( T item, int index, int count, IEqualityComparer equalityComparer )
- {
- return Values.IndexOf( item, index, count, equalityComparer );
- }
-
- public int IndexOf( T item )
- {
- return Values.IndexOf( item, 0, Count, EqualityComparer.Default );
- }
-
- public List Insert( int index, T element )
- {
- return this with { Values = Values.Insert( index, element ) };
- }
-
- public List InsertRange( int index, IEnumerable items )
- {
- return this with { Values = Values.InsertRange( index, items ) };
- }
-
- public int LastIndexOf( T item, int index, int count, IEqualityComparer equalityComparer )
- {
- return Values.LastIndexOf( item, index, count, equalityComparer );
- }
-
-
-
- public List Remove( T value )
- {
- return Remove( value, EqualityComparer.Default );
- }
-
- public List Remove( T value, IEqualityComparer equalityComparer )
- {
- return this with { Values = Values.Remove( value, equalityComparer ) };
- }
-
- public List RemoveAll( Predicate match )
- {
- return this with { Values = Values.RemoveAll( match ) };
- }
-
- public List RemoveAt( int index )
- {
- return this with { Values = Values.RemoveAt( index ) };
- }
-
- public List RemoveRange( IEnumerable items, IEqualityComparer equalityComparer )
- {
- return this with { Values = Values.RemoveRange( items, equalityComparer ) };
- }
-
- public List RemoveRange( int index, int count )
- {
- return this with { Values = Values.RemoveRange( index, count ) };
- }
-
- public List Replace( T oldValue, T newValue, IEqualityComparer equalityComparer )
- {
- return this with { Values = Values.Replace( oldValue, newValue, equalityComparer ) };
- }
-
- public List SetItem( int index, T value )
- {
- return this with { Values = Values.SetItem( index, value ) };
- }
-
- IEnumerator IEnumerable.GetEnumerator()
- {
- return (( IEnumerable )Values).GetEnumerator();
- }
-
- IImmutableList IImmutableList.Clear()
- {
- return Clear();
- }
-
- IImmutableList IImmutableList.Add( T value )
- {
- return Add( value );
- }
-
- IImmutableList IImmutableList.AddRange( IEnumerable items )
- {
- return AddRange( items );
- }
-
- IImmutableList IImmutableList.Insert( int index, T element )
- {
- return Insert( index, element );
- }
-
- IImmutableList IImmutableList.InsertRange( int index, IEnumerable items )
- {
- return InsertRange( index, items );
- }
-
- IImmutableList IImmutableList.Remove( T value, IEqualityComparer equalityComparer )
- {
- return Remove( value, equalityComparer );
- }
-
- IImmutableList IImmutableList.RemoveAll( Predicate match )
- {
- return RemoveAll( match );
- }
+ // --- IImmutableList implementation using the Change helper ---
+ public T this[int index] => Values[index];
+ public int Count => Values.Count;
+ public List Add(T value) => Change(v => v.Add(value));
+ public List AddRange(IEnumerable items) => Change(v => v.AddRange(items));
+ public List Clear() => Change(v => v.Clear());
+ // ... Implement all other IImmutableList methods similarly ...
+ #region IImmutableList Implementation
+ public List Insert( int index, T element ) => Change( v => v.Insert( index, element ) );
+ public List InsertRange( int index, IEnumerable items ) => Change( v => v.InsertRange( index, items ) );
+ public List Remove( T value, IEqualityComparer? equalityComparer ) => Change( v => v.Remove( value, equalityComparer ) );
+ public List Remove( T value ) => Remove( value, EqualityComparer.Default );
+ public List RemoveAll( Predicate match ) => Change( v => v.RemoveAll( match ) );
+ public List RemoveAt( int index ) => Change( v => v.RemoveAt( index ) );
+ public List RemoveRange( IEnumerable items, IEqualityComparer? equalityComparer ) => Change( v => v.RemoveRange( items, equalityComparer ) );
+ public List RemoveRange( int index, int count ) => Change( v => v.RemoveRange( index, count ) );
+ public List Replace( T oldValue, T newValue, IEqualityComparer? equalityComparer ) => Change( v => v.Replace( oldValue, newValue, equalityComparer ) );
+ public List SetItem( int index, T value ) => Change( v => v.SetItem( index, value ) );
+ public int IndexOf( T item, int index, int count, IEqualityComparer? equalityComparer ) => Values.IndexOf( item, index, count, equalityComparer ?? EqualityComparer.Default );
+ public int IndexOf( T item ) => IndexOf( item, 0, Count, EqualityComparer.Default );
+ public int LastIndexOf( T item, int index, int count, IEqualityComparer? equalityComparer ) => Values.LastIndexOf( item, index, count, equalityComparer ?? EqualityComparer.Default );
+ IImmutableList IImmutableList.Clear() => Clear();
+ IImmutableList IImmutableList.Add( T value ) => Add( value );
+ IImmutableList IImmutableList.AddRange( IEnumerable items ) => AddRange( items );
+ IImmutableList IImmutableList.Insert( int index, T element ) => Insert( index, element );
+ IImmutableList IImmutableList.InsertRange( int index, IEnumerable items ) => InsertRange( index, items );
+ IImmutableList IImmutableList.Remove( T value, IEqualityComparer? equalityComparer ) => Remove( value, equalityComparer );
+ IImmutableList IImmutableList.RemoveAll( Predicate match ) => RemoveAll( match );
IImmutableList IImmutableList.RemoveAt( int index ) => RemoveAt( index );
-
- IImmutableList IImmutableList.RemoveRange( IEnumerable items, IEqualityComparer equalityComparer )
- => RemoveRange( items, equalityComparer );
-
+ IImmutableList IImmutableList.RemoveRange( IEnumerable items, IEqualityComparer? equalityComparer ) => RemoveRange( items, equalityComparer );
IImmutableList IImmutableList.RemoveRange( int index, int count ) => RemoveRange( index, count );
-
- IImmutableList IImmutableList.Replace( T oldValue, T newValue, IEqualityComparer equalityComparer )
- => Replace( oldValue, newValue, equalityComparer );
-
+ IImmutableList IImmutableList.Replace( T oldValue, T newValue, IEqualityComparer? equalityComparer ) => Replace( oldValue, newValue, equalityComparer );
IImmutableList IImmutableList.SetItem( int index, T value ) => SetItem( index, value );
-}
+ #endregion
+ // --- Standard Interfaces ---
+ public IEnumerator GetEnumerator() => Values.GetEnumerator();
+ IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)Values).GetEnumerator();
+}
\ No newline at end of file
diff --git a/imm/iu.cs b/imm/iu.cs
index d048dfe..c1654b9 100644
--- a/imm/iu.cs
+++ b/imm/iu.cs
@@ -1,56 +1,47 @@
-
-// A spot for immutable helpers
-
-// TODO
-// TODO
-// TODO
-// x) Wrap metadata into its own struct
-// x) Make metadata a struct vs a class
+#nullable enable
using System;
using System.Runtime.CompilerServices;
+using imm;
-
-static public class iu
+///
+/// Helper static class for processing immutable objects using a 'ref' pattern.
+/// Provides different levels of processing based on the type.
+///
+public static class iu
{
- //This can handle both Timed and Recorded
- static public T Process( ref T obj, Func fn,
- string reason = "",
- [CallerMemberName] string dbgName = "",
- [CallerFilePath] string dbgPath = "",
- [CallerLineNumber] int dbgLine = 0,
- [CallerArgumentExpression("fn")]
- string dbgExpression = default )
- where T : imm.Recorded, imm.Imm
- {
- obj = obj.Process( fn, reason, dbgName, dbgPath, dbgLine, dbgExpression );
- return obj;
- }
+ ///
+ /// Processes a 'Versioned' object (Level 1).
+ ///
+ public static T LightProcess(
+ ref T obj,
+ Func fn,
+ string reason = "Processed")
+ where T : Versioned
+ {
+ obj = obj.Process(fn, reason);
+ return obj;
+ }
- static public imm.Timed Process( ref imm.Timed obj, Func fn,
- string reason = "",
- [CallerMemberName] string dbgName = "",
- [CallerFilePath] string dbgPath = "",
- [CallerLineNumber] int dbgLine = 0,
- [CallerArgumentExpression("fn")]
- string dbgExpression = default )
- where T : imm.Timed, imm.Imm
- {
- obj = obj.Process( fn, reason, dbgName, dbgPath, dbgLine, dbgExpression );
- return obj;
- }
+ ///
+ /// Processes a 'Recorded' object (Level 2), capturing caller info.
+ ///
+ public static T Process(
+ ref T obj,
+ Func fn,
+ string reason = "",
+ [CallerMemberName] string dbgName = "",
+ [CallerFilePath] string dbgPath = "",
+ [CallerLineNumber] int dbgLine = 0,
+ [CallerArgumentExpression("fn")] string dbgExpression = "")
+ where T : Recorded // Catches Recorded and Timed
+ {
+ // This will call the 'Timed' override if T is Timed,
+ // or the 'Recorded' override if T is Recorded.
+ obj = obj.Process(fn, reason, dbgName, dbgPath, dbgLine, dbgExpression);
+ return obj;
+ }
-
- static public T LightProcess( ref T obj, Func fn,
- string reason = "",
- [CallerMemberName] string dbgName = "",
- [CallerFilePath] string dbgPath = "",
- [CallerLineNumber] int dbgLine = 0,
- [CallerArgumentExpression("fn")]
- string dbgExpression = default )
- where T : imm.Versioned, imm.Imm
- {
- obj = obj.Process( fn, reason );
- return obj;
- }
-}
+ // No specific Process needed for Timed, as it's caught by Recorded
+ // and its Process override handles the timing.
+}
\ No newline at end of file
diff --git a/logging/Log.cs b/logging/Log.cs
index fddd01f..06b590c 100644
--- a/logging/Log.cs
+++ b/logging/Log.cs
@@ -45,9 +45,9 @@ public record struct Value( T _val, string _exp = "" )
[CallerArgumentExpression("v")]
string dbgExp = ""
)
- {
- return new( v, dbgExp );
- }
+ {
+ return new( v, dbgExp );
+ }
}
public struct SourceLoc
@@ -55,11 +55,11 @@ public struct SourceLoc
readonly string _reason = "";
readonly string _dbgName = "";
readonly string _dbgPath = "";
- readonly int _dbgLine = -1;
+ readonly int _dbgLine = -1;
public SourceLoc( string reason, string dbgName, string dbgPath, int dbgLine )
{
- _reason = reason;
+ _reason = reason;
_dbgName = dbgName;
_dbgPath = dbgPath;
_dbgLine = dbgLine;
@@ -129,7 +129,7 @@ static public class log
static void StartGCWatcher()
{
- while( !s_running )
+ while( s_threading != ThreadState.Running )
{
Thread.Sleep( 10 );
}
@@ -140,7 +140,7 @@ static public class log
static void StartTracing()
{
- while( !s_running )
+ while( s_threading != ThreadState.Running )
{
Thread.Sleep( 10 );
}
@@ -154,9 +154,9 @@ static public class log
[CallerArgumentExpression("val")]
string dbgExp = ""
)
- {
- return new( val, dbgExp );
- }
+ {
+ return new( val, dbgExp );
+ }
[Flags]
public enum LogType
@@ -255,7 +255,7 @@ static public class log
static public void endpointForCat( string cat, Endpoints ep )
{
- ImmutableInterlocked.AddOrUpdate( ref s_logEPforCat, cat, ep, (k, v) => ep );
+ ImmutableInterlocked.AddOrUpdate( ref s_logEPforCat, cat, ep, ( k, v ) => ep );
}
@@ -284,7 +284,7 @@ static public class log
static int s_cwdLength = s_cwd.Length;
static ImmutableDictionary s_files = ImmutableDictionary.Empty;
-#region Util
+ #region Util
static public (string, string, int) record( [CallerMemberName] string dbgName = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1 )
=> (dbgName, dbgPath, dbgLine);
@@ -331,11 +331,11 @@ static public class log
{
return relativePath( path );
}
-#endregion // Util
+ #endregion // Util
-#region Forwards
- static public T call( Func func, [CallerMemberName] string dbgName = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1, [CallerArgumentExpression("func")] string dbgExp = "" )
+ #region Forwards
+ static public T call( Func func, [CallerMemberName] string dbgName = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1, [CallerArgumentExpression( "func" )] string dbgExp = "" )
{
log.info( $"Calling {dbgExp}" );
var val = func();
@@ -343,53 +343,53 @@ static public class log
return val;
}
- static public T var( T val, [CallerMemberName] string dbgName = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1, [CallerArgumentExpression("val")] string dbgExp = "" )
+ static public T var( T val, [CallerMemberName] string dbgName = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1, [CallerArgumentExpression( "val" )] string dbgExp = "" )
{
log.info( $"Called {dbgExp} Got: {val}" );
return val;
}
- static public void call( Action func, [CallerMemberName] string dbgName = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1, [CallerArgumentExpression("func")] string dbgExp = "" )
+ static public void call( Action func, [CallerMemberName] string dbgName = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1, [CallerArgumentExpression( "func" )] string dbgExp = "" )
{
log.info( $"Calling {dbgExp}" );
func();
log.info( $"| Done" );
}
- static public void fatal( string msg, string cat = "", object? obj = null, [CallerFilePath] string path = "", [CallerLineNumber] int line = -1, [CallerMemberName] string member = "", [CallerArgumentExpression("msg")] string dbgExp = "" )
+ static public void fatal( string msg, string cat = "", object? obj = null, [CallerFilePath] string path = "", [CallerLineNumber] int line = -1, [CallerMemberName] string member = "", [CallerArgumentExpression( "msg" )] string dbgExp = "" )
{
logBase( msg, LogType.Fatal, path, line, member, cat, dbgExp, obj );
}
[StackTraceHidden]
- static public void error( string msg, string cat = "", object? obj = null, [CallerFilePath] string path = "", [CallerLineNumber] int line = -1, [CallerMemberName] string member = "", [CallerArgumentExpression("msg")] string dbgExp = "" )
+ static public void error( string msg, string cat = "", object? obj = null, [CallerFilePath] string path = "", [CallerLineNumber] int line = -1, [CallerMemberName] string member = "", [CallerArgumentExpression( "msg" )] string dbgExp = "" )
{
logBase( msg, LogType.Error, path, line, member, cat, dbgExp, obj );
}
[StackTraceHidden]
- static public void warn( string msg, string cat = "", object? obj = null, [CallerFilePath] string path = "", [CallerLineNumber] int line = -1, [CallerMemberName] string member = "", [CallerArgumentExpression("msg")] string dbgExp = "" )
+ static public void warn( string msg, string cat = "", object? obj = null, [CallerFilePath] string path = "", [CallerLineNumber] int line = -1, [CallerMemberName] string member = "", [CallerArgumentExpression( "msg" )] string dbgExp = "" )
{
logBase( msg, LogType.Warn, path, line, member, cat, dbgExp, obj );
}
- static public void high( string msg, string cat = "", object? obj = null, [CallerFilePath] string path = "", [CallerLineNumber] int line = -1, [CallerMemberName] string member = "", [CallerArgumentExpression("msg")] string dbgExp = "" )
+ static public void high( string msg, string cat = "", object? obj = null, [CallerFilePath] string path = "", [CallerLineNumber] int line = -1, [CallerMemberName] string member = "", [CallerArgumentExpression( "msg" )] string dbgExp = "" )
{
logBase( msg, LogType.High, path, line, member, cat, dbgExp, obj );
}
static public void info( string msg, string cat = "", object? obj = null,
- [CallerFilePath] string path = "", [CallerLineNumber] int line = -1, [CallerMemberName] string member = "", [CallerArgumentExpression("msg")] string dbgExp = "" )
+ [CallerFilePath] string path = "", [CallerLineNumber] int line = -1, [CallerMemberName] string member = "", [CallerArgumentExpression( "msg" )] string dbgExp = "" )
{
logBase( msg, LogType.Info, path, line, member, cat, dbgExp, obj );
}
- static public void debug( string msg, string cat = "", object? obj = null, [CallerFilePath] string path = "", [CallerLineNumber] int line = -1, [CallerMemberName] string member = "", [CallerArgumentExpression("msg")] string dbgExp = "" )
+ static public void debug( string msg, string cat = "", object? obj = null, [CallerFilePath] string path = "", [CallerLineNumber] int line = -1, [CallerMemberName] string member = "", [CallerArgumentExpression( "msg" )] string dbgExp = "" )
{
logBase( msg, LogType.Debug, path, line, member, cat, dbgExp, obj );
}
- static public void trace( string msg, string cat = "", object? obj = null, [CallerFilePath] string path = "", [CallerLineNumber] int line = -1, [CallerMemberName] string member = "", [CallerArgumentExpression("msg")] string dbgExp = "" )
+ static public void trace( string msg, string cat = "", object? obj = null, [CallerFilePath] string path = "", [CallerLineNumber] int line = -1, [CallerMemberName] string member = "", [CallerArgumentExpression( "msg" )] string dbgExp = "" )
{
logBase( msg, LogType.Trace, path, line, member, cat, dbgExp, obj );
}
@@ -401,19 +401,19 @@ static public class log
{
}
- static public void info( string msg, A a, string cat = "", [CallerArgumentExpression("a")] string dbgExpA = "" )
+ static public void info( string msg, A a, string cat = "", [CallerArgumentExpression( "a" )] string dbgExpA = "" )
{
}
- static public void info( string msg, object a, object b, string cat = "", [CallerArgumentExpression("a")] string dbgExpA = "", [CallerArgumentExpression("a")] string dbgExpB = "" )
+ static public void info( string msg, object a, object b, string cat = "", [CallerArgumentExpression( "a" )] string dbgExpA = "", [CallerArgumentExpression( "a" )] string dbgExpB = "" )
{
}
#endregion
-#region Helpers
- static public void logProps( object obj, string header, LogType type = LogType.Debug, string cat = "", string prefix = "", [CallerFilePath] string path = "", [CallerLineNumber] int line = -1, [CallerMemberName] string member = "", [CallerArgumentExpression("obj")] string dbgExpObj = "" )
+ #region Helpers
+ static public void logProps( object obj, string header, LogType type = LogType.Debug, string cat = "", string prefix = "", [CallerFilePath] string path = "", [CallerLineNumber] int line = -1, [CallerMemberName] string member = "", [CallerArgumentExpression( "obj" )] string dbgExpObj = "" )
{
var list = refl.GetAllProperties( obj.GetType() );
@@ -434,7 +434,7 @@ static public class log
}
catch( Exception ex )
{
- logBase( $"Exception processing {pi.Name} {ex.Message}", LogType.Error, path, line, member, cat, dbgExpObj, obj);
+ logBase( $"Exception processing {pi.Name} {ex.Message}", LogType.Error, path, line, member, cat, dbgExpObj, obj );
}
}
@@ -454,7 +454,7 @@ static public class log
log.warn( $"Got {notExpectedValue} instead of {value}{falseString}" );
}
}
-#endregion
+ #endregion
static object s_lock = new object();
@@ -516,41 +516,41 @@ static public class log
lock( s_lock )
{
//We're already running, so just tell folks, and jump back
- if( s_running )
+ if( s_threading == ThreadState.Running )
{
log.info( $"Already running, so this is a NOP" );
}
- s_running = true;
+ s_threading = ThreadState.Running;
- s_startTime = DateTime.Now;
+ s_startTime = DateTime.Now;
- s_cwd = Directory.GetCurrentDirectory();
- s_cwdLength = s_cwd.Length;
+ s_cwd = Directory.GetCurrentDirectory();
+ s_cwdLength = s_cwd.Length;
- s_endpoints = endpoints;
+ s_endpoints = endpoints;
- var dir = Path.GetDirectoryName( filename );
+ var dir = Path.GetDirectoryName( filename );
- if( dir?.Length > 0 )
- {
- Directory.CreateDirectory( dir );
- }
+ if( dir?.Length > 0 )
+ {
+ Directory.CreateDirectory( dir );
+ }
- s_stream = new FileStream( filename, FileMode.Append, FileAccess.Write );
- s_writer = new StreamWriter( s_stream, Encoding.UTF8, 128, true );
+ s_stream = new FileStream( filename, FileMode.Append, FileAccess.Write );
+ s_writer = new StreamWriter( s_stream, Encoding.UTF8, 128, true );
- //s_errorStream = new FileStream( filename + ".error", FileMode.Append, FileAccess.Write );
- //s_errorWriter = new StreamWriter( s_errorStream );
+ //s_errorStream = new FileStream( filename + ".error", FileMode.Append, FileAccess.Write );
+ //s_errorWriter = new StreamWriter( s_errorStream );
- {
+ {
var time = DateTime.Now;
// Header for this run
var blankLine = new LogEvent( LogType.Raw, $"", "", 0, "", "lib.time", "", null );
var beginLine = new LogEvent( LogType.Raw, $"Begin B E G I N ******************************************************************************************************************", "", 0, "", "lib.time", "", null );
- var timeLine = new LogEvent( LogType.Raw, $"D A T E {time.Year}/{time.Month.ToString("00")}/{time.Day.ToString("00")} T I M E {time.Hour.ToString("00")}:{time.Minute.ToString("00")}:{time.Second.ToString("00")}.{time.Millisecond.ToString("000")}{time.Microsecond.ToString("000")}", "", 0, "", "lib.time", "", null );
+ var timeLine = new LogEvent( LogType.Raw, $"D A T E {time.Year}/{time.Month.ToString( "00" )}/{time.Day.ToString( "00" )} T I M E {time.Hour.ToString( "00" )}:{time.Minute.ToString( "00" )}:{time.Second.ToString( "00" )}.{time.Millisecond.ToString( "000" )}{time.Microsecond.ToString( "000" )}", "", 0, "", "lib.time", "", null );
//writeToAll( endLine );
@@ -561,61 +561,88 @@ static public class log
writeToAll( timeLine );
writeToAll( blankLine );
+ }
+
+ LogEvent msgStartupBegin = new LogEvent( LogType.Info, $"startup BEGIN", "", 0, "", "log.startup", "", null );
+ writeToAll( msgStartupBegin );
+
+ LogEvent msgFilename = new LogEvent( LogType.Info, $"Logging in {filename}", "", 0, "", "log.startup", "", null );
+ writeToAll( msgFilename );
+
+ StartThread();
+
+
+
+ //info( $"Logging in {filename}" );
+
+
+ LogGC.RegisterObjectId( s_lock );
+
+ //Debug.Listeners.Add( this );
+
+ //var evt = CreateLogEvent( LogType.Info, $"startup", "System", null );
+
+ //s_events.Enqueue( evt );
+
+ /*
+ if( (endpoints & Endpoints.Console) == Endpoints.Console )
+ {
+ addDelegate(WriteToConsole);
+ }
+ */
+
+
+ //LogEvent msgStartupBegin = new LogEvent( LogType.Info, $"startup END", "", 0, "", "lib.time", "", null );
+ //writeToAll( msgStartupBegin );
+
+ info( $"startup END", cat: "log.startup" );
}
- LogEvent msgStartupBegin = new LogEvent( LogType.Info, $"startup BEGIN", "", 0, "", "log.startup", "", null );
- writeToAll( msgStartupBegin );
+ }
- LogEvent msgFilename = new LogEvent( LogType.Info, $"Logging in {filename}", "", 0, "", "log.startup", "", null );
- writeToAll( msgFilename );
-
- var start = new ThreadStart( run );
+ private static void StartThread()
+ {
+ var start = new ThreadStart( threadLoop );
s_thread = new Thread( start );
s_thread.Priority = ThreadPriority.BelowNormal;
s_thread.Name = $"Logging";
s_thread.Start();
-
-
-
- //info( $"Logging in {filename}" );
-
-
- LogGC.RegisterObjectId( s_lock );
-
- //Debug.Listeners.Add( this );
-
- //var evt = CreateLogEvent( LogType.Info, $"startup", "System", null );
-
- //s_events.Enqueue( evt );
-
- /*
- if( (endpoints & Endpoints.Console) == Endpoints.Console )
- {
- addDelegate(WriteToConsole);
- }
- */
-
-
- //LogEvent msgStartupBegin = new LogEvent( LogType.Info, $"startup END", "", 0, "", "lib.time", "", null );
- //writeToAll( msgStartupBegin );
-
- info( $"startup END", cat: "log.startup" );
- }
-
}
- static bool s_running = false;
-
- static void run()
+ private static void StopThread( ThreadState thread )
{
- while( !s_running )
+ log.info( $"Setting thread to {thread}");
+ s_threading = thread;
+ int count = 0;
+ while( s_thread.IsAlive )
+ {
+ Thread.Sleep( 0 );
+ count++;
+ }
+
+ Console.WriteLine( $"Waited {count} loops" );
+ }
+
+ public enum ThreadState
+ {
+ Invalid,
+ Running,
+ Paused,
+ Finished,
+ }
+
+ static ThreadState s_threading = ThreadState.Invalid;
+
+ static void threadLoop()
+ {
+ while( s_threading != ThreadState.Running )
{
// TODO PERF Replace this with a semaphore/mutex
Thread.Sleep( 1 );
}
- while( s_running )
+ while( s_threading != ThreadState.Running )
{
while( s_events.TryDequeue( out var evt ) )
{
@@ -626,22 +653,18 @@ static public class log
Thread.Sleep( 1 );
}
- var endLine = new LogEvent( LogType.Raw, $"End E N D ******************************************************************************************************************", "", 0, "", "lib.time", "", null );
- writeToAll( endLine );
-
+ //var endLine = new LogEvent( LogType.Raw, $"End E N D ******************************************************************************************************************", "", 0, "", "lib.time", "", null );
+ var endLine = new LogEvent( LogType.Raw, $"Thread state {s_threading} ******************************************************************************************************************", "", 0, "", "lib.time", "", null );
+ writeToAll( endLine );
}
public static void stop()
{
- if( !s_running ) return;
+ if( s_threading == ThreadState.Finished )
+ return;
- s_running = false;
-
- while( s_thread.IsAlive )
- {
- Thread.Sleep( 0 );
- }
+ StopThread( ThreadState.Finished );
s_writer?.Close();
s_stream?.Close();
@@ -651,6 +674,24 @@ static public class log
}
+ public static void pauseThread()
+ {
+ log.info("Pausing thread" );
+
+ s_threading = ThreadState.Paused;
+
+ StopThread( ThreadState.Paused );
+ }
+
+ public static void unpauseThread()
+ {
+ s_threading = ThreadState.Running;
+
+ StartThread();
+ }
+
+
+
static public void addDelegate( Log_delegate cb )
{
s_delegates.Add( cb );
@@ -660,14 +701,22 @@ static public class log
{
switch( type )
{
- case LogType.Trace: return ' ';
- case LogType.Debug: return ' ';
- case LogType.Info: return ' ';
- case LogType.High: return '+';
- case LogType.Warn: return '+';
- case LogType.Error: return '*';
- case LogType.Fatal: return '*';
- default: return '?';
+ case LogType.Trace:
+ return ' ';
+ case LogType.Debug:
+ return ' ';
+ case LogType.Info:
+ return ' ';
+ case LogType.High:
+ return '+';
+ case LogType.Warn:
+ return '+';
+ case LogType.Error:
+ return '*';
+ case LogType.Fatal:
+ return '*';
+ default:
+ return '?';
}
}
@@ -725,7 +774,7 @@ static public class log
var truncatedCat = evt.Cat.Substring( 0, Math.Min( s_catWidth, evt.Cat.Length ) );
- var timeHdr = $"{s_timeHeader}{((int)span.TotalMinutes).ToString("000")}:{span.Seconds.ToString("D2")}.{span.Milliseconds.ToString("000")}";
+ var timeHdr = $"{s_timeHeader}{( (int)span.TotalMinutes ).ToString( "000" )}:{span.Seconds.ToString( "D2" )}.{span.Milliseconds.ToString( "000" )}";
var msgHdr = string.Format( $"{timeHdr} | {{0,-{s_catWidth}}}{{1}}| ", truncatedCat, sym );
@@ -750,17 +799,20 @@ static public class log
var truncatedCat = evt.Cat.Substring( 0, Math.Min( s_catWidth, evt.Cat.Length ) );
- if( string.IsNullOrWhiteSpace( truncatedCat) ) truncatedCat = $"B R O K E N truncatedCat";
+ if( string.IsNullOrWhiteSpace( truncatedCat ) )
+ truncatedCat = $"B R O K E N truncatedCat";
//Dont really need the year-month-day frankly.
//var timeHdr = $"{evt.Time.Year}-{evt.Time.Month.ToString("00")}-{evt.Time.Day.ToString("00")} {evt.Time.Hour.ToString("00")}:{evt.Time.Minute.ToString("00")}:{evt.Time.Second.ToString("00")}.{evt.Time.Millisecond.ToString("000")}{evt.Time.Microsecond.ToString("000")}";
- var timeHdr = $"{evt.Time.Hour.ToString("00")}:{evt.Time.Minute.ToString("00")}:{evt.Time.Second.ToString("00")}.{evt.Time.Millisecond.ToString("000")}{evt.Time.Microsecond.ToString("000")}";
+ var timeHdr = $"{evt.Time.Hour.ToString( "00" )}:{evt.Time.Minute.ToString( "00" )}:{evt.Time.Second.ToString( "00" )}.{evt.Time.Millisecond.ToString( "000" )}{evt.Time.Microsecond.ToString( "000" )}";
- if( string.IsNullOrWhiteSpace( timeHdr) ) timeHdr = $"B R O K E N timeHdr";
+ if( string.IsNullOrWhiteSpace( timeHdr ) )
+ timeHdr = $"B R O K E N timeHdr";
var msgHdr = string.Format( $"{timeHdr} | {{0,-{s_catWidth}}}{{1}}| ", truncatedCat, sym );
- if( string.IsNullOrWhiteSpace( msgHdr) ) msgHdr = $"B R O K E N msgHdr";
+ if( string.IsNullOrWhiteSpace( msgHdr ) )
+ msgHdr = $"B R O K E N msgHdr";
return msgHdr;
@@ -793,7 +845,7 @@ static public class log
var msg = evt.Msg;
- if( (string.IsNullOrWhiteSpace(msg) && evt.LogType != LogType.Raw) || msg.Contains( (char)0 ) )
+ if( ( string.IsNullOrWhiteSpace( msg ) && evt.LogType != LogType.Raw ) || msg.Contains( (char)0 ) )
{
msg = "B R O K E N msg";
}
@@ -831,7 +883,7 @@ static public class log
{
s_lastDisplaySeconds = curSeconds;
- var minuteEvt = new LogEvent( LogType.Raw, $"T I M E ==> {evt.Time.Hour.ToString("00")}:{evt.Time.Minute.ToString("00")}:{evt.Time.Second.ToString("00")}.{evt.Time.Millisecond.ToString("000")} : {evt.Time.ToShortDateString()}", "", 0, "", "lib.time", "", null );
+ var minuteEvt = new LogEvent( LogType.Raw, $"T I M E ==> {evt.Time.Hour.ToString( "00" )}:{evt.Time.Minute.ToString( "00" )}:{evt.Time.Second.ToString( "00" )}.{evt.Time.Millisecond.ToString( "000" )} : {evt.Time.ToShortDateString()}", "", 0, "", "lib.time", "", null );
minuteEvt.Time = evt.Time;
writeSpecialEvent( minuteEvt );
}
@@ -893,7 +945,7 @@ static public class log
}
catch( Exception ex )
{
- #region Catch
+ #region Catch
Console.WriteLine( "EXCEPTION DURING LOGGING" );
Console.WriteLine( "EXCEPTION DURING LOGGING" );
Console.WriteLine( "EXCEPTION DURING LOGGING" );
@@ -907,7 +959,7 @@ static public class log
Debug.WriteLine( "EXCEPTION DURING LOGGING" );
Debug.WriteLine( "EXCEPTION DURING LOGGING" );
Debug.WriteLine( $"Exception {ex}" );
- #endregion
+ #endregion
}
}
diff --git a/res/Resource.cs b/res/Resource.cs
index 86ff5fe..a50323b 100644
--- a/res/Resource.cs
+++ b/res/Resource.cs
@@ -1,444 +1,434 @@
using System;
using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-//using System.Threading.Tasks;
using System.Diagnostics;
+using System.IO;
using System.Reflection;
using System.Collections.Immutable;
-using System.Threading;
-using System.IO;
-using Microsoft.CodeAnalysis;
using System.Runtime.CompilerServices;
+using System.Threading;
+using Microsoft.CodeAnalysis; // Note: This was in the original, but seems unused. Keep for now.
+using System.Linq;
#nullable enable
namespace res;
+// A delegate representing a function that can load a resource of type T.
+public delegate T Load( string filename );
-using ImmDefLoad = ImmutableQueue<(string name, Ref)>;
-
-public interface Res_old
+///
+/// Abstract base class for a resource reference.
+/// Provides a common way to refer to resources and includes debugging/tracking info.
+///
+[DebuggerDisplay( "Path = {Filename}" )]
+public abstract class Ref
{
+ public static bool VerboseLogging { get; set; } = false;
+ public string Filename { get; }
+ public string Reason { get; }
+ public string DbgName { get; }
+ public string DbgPath { get; }
+ public int DbgLine { get; }
+
+ protected Ref(
+ string filename,
+ string reason = "",
+ [CallerMemberName] string dbgName = "",
+ [CallerFilePath] string dbgPath = "",
+ [CallerLineNumber] int dbgLine = 0 )
+ {
+ Filename = filename;
+ Reason = reason;
+ DbgName = dbgName;
+ DbgPath = dbgPath;
+ DbgLine = dbgLine;
+
+ if( VerboseLogging )
+ log.info( $"Ref Created: {GetType().Name} {Filename}" );
+ }
+
+ ///
+ /// Looks up and loads the resource.
+ ///
+ /// The loaded resource object.
+ public abstract object Lookup(
+ string reason = "",
+ [CallerMemberName] string dbgName = "",
+ [CallerFilePath] string dbgPath = "",
+ [CallerLineNumber] int dbgLine = 0 );
+
+ ///
+ /// Called when the resource might have changed (optional, base implementation does nothing).
+ ///
+ public virtual void OnChange() { }
+
+ ///
+ /// Internal method to trigger the initial load (used by deferred loading, if implemented).
+ ///
+ internal virtual void InternalLoad() { }
}
-[DebuggerDisplay("Path = {path}")]
-abstract public class Ref : lib.I_Serialize
-{
- static public bool s_verboseLogging = false;
-
- public string Filename =>path;
-
-
- public Ref( string filename = "{empty_filename}",
- string reason = "",
- [CallerMemberName] string dbgName = "",
- [CallerFilePath] string dbgPath = "",
- [CallerLineNumber] int dbgLine = 0
- )
- {
- path = filename;
- if( s_verboseLogging ) log.info( $"Ref: {GetType().Name} {path}" );
-
- _reason = reason;
- _dbgName = dbgName;
- _dbgPath = dbgPath;
- _dbgLine = dbgLine;
- }
-
- abstract public object lookup(
- string reason = "",
- [CallerMemberName] string dbgName = "",
- [CallerFilePath] string dbgPath = "",
- [CallerLineNumber] int dbgLine = 0
-
- );
-
-
- virtual public void OnChange()
- {
- }
-
- virtual internal void load()
- {
-
- }
-
- private string path = "{set_from_inline_cons}";
-
- protected string _reason = "";
- protected string _dbgName = "";
- protected string _dbgPath = "";
- protected int _dbgLine = 0;
- protected string _dbgExp = "";
-
-
-}
-
+///
+/// A typed reference to a resource of type T.
+/// Handles lazy loading and access to the resource.
+///
[Serializable]
-[DebuggerDisplay("Path = {path} / Res = {res}")]
+[DebuggerDisplay( "Path = {Filename} / Res = {m_res}" )]
public class Ref : Ref where T : class, new()
{
- public T res => m_res != null ? m_res : lookup();
+ [NonSerialized]
+ private T? m_res;
- override public T lookup(
- string reason = "",
- [CallerMemberName] string dbgName = "",
- [CallerFilePath] string dbgPath = "",
- [CallerLineNumber] int dbgLine = 0
+ ///
+ /// Gets the resource, loading it if necessary.
+ ///
+ public T Res => m_res ?? Lookup();
- )
+ ///
+ /// Gets the resource, loading it if necessary.
+ ///
+ //[Deprecated("Use Res property instead.")]
+ public T res => m_res ?? Lookup();
+
+ public Ref(
+ string filename = "",
+ string reason = "",
+ [CallerMemberName] string dbgName = "",
+ [CallerFilePath] string dbgPath = "",
+ [CallerLineNumber] int dbgLine = 0 )
+ : base(
+ !string.IsNullOrWhiteSpace( filename ) ? filename : $"{{{dbgName}_{Path.GetFileNameWithoutExtension( dbgPath )}}}",
+ reason, dbgName, dbgPath, dbgLine )
{
- m_res = Mgr.load( Filename, $"Ref lookup", dbgName, dbgPath, dbgLine );
- if( s_verboseLogging ) log.info( $"Ref.lookup {GetType().Name} {GetType().GenericTypeArguments[0]} path {Filename}" );
- return m_res;
- }
-
- //For serialization
- /*
- public Ref()
- :
- base( "{set_from_ref<>_default_cons}" )
- {
- if( s_verboseLogging ) log.info( $"Ref {GetType().Name} {GetType().GenericTypeArguments[0]} path {Filename}" );
- }
- */
-
-
- public Ref( string filename = "",
- string reason = "",
- [CallerMemberName] string dbgName = "",
- [CallerFilePath] string dbgPath = "",
- [CallerLineNumber] int dbgLine = 0
- )
- :
- base( !string.IsNullOrWhiteSpace(filename) ? filename : $"{{{dbgName}_{log.whatFile(dbgPath)}}}" , reason, dbgName, dbgPath, dbgLine )
- {
- if( s_verboseLogging ) log.info( $"Ref {GetType().Name} {GetType().GenericTypeArguments[0]} path {Filename}" );
+ if( VerboseLogging )
+ log.info( $"Ref Created: {GetType().Name}<{typeof( T ).Name}> {Filename}" );
m_res = default;
}
-
- override internal void load()
+ ///
+ /// Looks up and loads the resource.
+ ///
+ public override T Lookup(
+ string reason = "",
+ [CallerMemberName] string dbgName = "",
+ [CallerFilePath] string dbgPath = "",
+ [CallerLineNumber] int dbgLine = 0 )
{
- m_res = Mgr.load( Filename, _reason, _dbgName, _dbgPath, _dbgLine );
- if( s_verboseLogging ) log.info( $"Ref.load {GetType().Name} {GetType().GenericTypeArguments[0]} path {Filename}" );
+ // If already loaded, return it.
+ if( m_res != null )
+ return m_res;
+
+ // Load using the Mgr.
+ m_res = Mgr.Load( Filename, $"Ref lookup (orig: {Reason}) bcs {reason}", dbgName, dbgPath, dbgLine );
+ if( VerboseLogging )
+ log.info( $"Ref.Lookup: {GetType().Name}<{typeof( T ).Name}> {Filename}" );
+ return m_res;
}
- public object OnDeserialize( object enclosing )
+ /*
+ ///
+ /// Overrides the base Lookup to provide a typed result.
+ ///
+ public override object Lookup(
+ string reason = "",
+ [CallerMemberName] string dbgName = "",
+ [CallerFilePath] string dbgPath = "",
+ [CallerLineNumber] int dbgLine = 0)
{
- return enclosing;
+ return Lookup(reason, dbgName, dbgPath, dbgLine);
+ }
+ */
+
+ ///
+ /// Internal load implementation.
+ ///
+ internal override void InternalLoad()
+ {
+ if( m_res == null )
+ {
+ m_res = Mgr.Load( Filename, Reason, DbgName, DbgPath, DbgLine );
+ if( VerboseLogging )
+ log.info( $"Ref.InternalLoad: {GetType().Name}<{typeof( T ).Name}> {Filename}" );
+ }
}
- static public Ref createAsset( T v, string path,
- string reason = "",
- [CallerMemberName] string dbgName = "",
- [CallerFilePath] string dbgPath = "",
- [CallerLineNumber] int dbgLine = 0
- )
+ ///
+ /// Creates a new resource asset and saves it, returning a Ref to it.
+ /// Handles existing files by renaming them.
+ ///
+ public static Ref CreateAsset(
+ T value, string path,
+ string reason = "",
+ [CallerMemberName] string dbgName = "",
+ [CallerFilePath] string dbgPath = "",
+ [CallerLineNumber] int dbgLine = 0 )
{
if( File.Exists( path ) )
{
- log.warn( $"For {typeof(T).Name}, saving asset to {path}, but it already exists" );
-
- var newPath = $"{path}_{DateTime.Now.ToShortDateString()}_{DateTime.Now.ToShortTimeString()}";
-
- System.IO.File.Move(path, newPath );
-
- log.warn( $"For {typeof(T).Name}, renamed to {newPath}" );
+ log.warn( $"Asset exists: {path}. Renaming before creating." );
+ var newPath = $"{path}_{DateTime.Now:yyyyMMdd_HHmmss}";
+ File.Move( path, newPath );
+ log.warn( $"Renamed existing asset to: {newPath}" );
}
- var createReason = $"Type {v.GetType().Name}";
+ // Here you would typically save 'value' to 'path' using a registered saver or specific logic.
+ // Since saving isn't defined, we'll assume it happens externally or add it here if needed.
+ // For now, we'll just log and create the Ref.
+ log.info( $"Creating asset Ref for {typeof( T ).Name} at {path}." );
- if( v is imm.Imm imm )
- {
- createReason = $"{imm?.Meta}";
- }
+ // We need a way to save 'value' to 'path' before creating the Ref.
+ // This part requires a 'Save' function, which isn't in the original.
+ // Let's assume you'll add saving logic here.
+ // Mgr.Save(value, path); // Example: Needs implementation
- var newRef = new Ref( path, $"createAsset {createReason} bcs {reason}", dbgName, dbgPath, dbgLine );
+ var immMeta = (value as imm.Obj)?.Meta;
+
+
+
+ var createReason = $"CreateAsset: {value.GetType().Name} - {immMeta?.Reason ?? "N/A"}";
+ var newRef = new Ref( path, $"{createReason} bcs {reason}", dbgName, dbgPath, dbgLine );
+
+ // We should make the newRef hold the 'value' immediately,
+ // or ensure loading it back gives the same 'value'.
+ // Setting m_res directly might be an option, or caching it.
+ newRef.m_res = value;
+ Mgr.CacheResource( path, value, $"CreateAsset {typeof( T ).Name}" );
return newRef;
}
-
- [NonSerialized]
- protected T? m_res;
}
-public class Resource
+///
+/// Holds a weak reference to a cached resource along with metadata.
+///
+internal record ResourceHolder( WeakReference WeakRef, string Name, DateTime Captured ) where T : class;
+
+///
+/// Manages resource loading, caching, and loader registration.
+/// This static class replaces the original `Mgr` instance.
+///
+public static class Mgr
{
- static public Mgr mgr = new();
-}
-
-public delegate T Load( string filename );
-
-
-abstract class LoadHolder
-{
- public abstract object load();
-}
-
-
-class LoadHolder : LoadHolder
-{
- public LoadHolder( Load fnLoad )
+ // Internal holder for type-specific loaders.
+ private abstract class LoadHolder { public abstract object Load( string filename ); }
+ private class LoadHolder : LoadHolder
{
- _fnLoad = fnLoad;
+ private readonly Load _fnLoad;
+ public LoadHolder( Load fnLoad ) { _fnLoad = fnLoad; }
+ public override object Load( string filename ) => _fnLoad( filename )!;
}
- public Load _fnLoad;
-
- public override object load()
- {
- return load();
- }
-}
-
-public record class ResourceHolder( WeakReference weak, string Name, DateTime captured ) : imm.Recorded>
-where T : class
-{
-
-}
-
-//generic classes make a new static per generic type
-class ResCache where T : class, new()
-{
- public static T s_default = new();
- public static ImmutableDictionary