├── .editorconfig
├── .gitattributes
├── .github
└── workflows
│ ├── auto_release.yaml
│ ├── publish.yaml
│ ├── spellcheck.yaml
│ └── tests.yaml
├── .gitignore
├── .vscode
├── extensions.json
├── launch.json
├── settings.json
└── tasks.json
├── CONTRIBUTING.md
├── Chickensoft.GoDotNet.Tests
├── Chickensoft.GoDotNet.Tests.csproj
├── Chickensoft.GoDotNet.Tests.sln
├── badges
│ ├── .gdignore
│ ├── branch_coverage.svg
│ └── line_coverage.svg
├── coverage.sh
├── coverage
│ └── .gdignore
├── icon.svg
├── icon.svg.import
├── nuget.config
├── project.godot
└── test
│ ├── Tests.cs
│ ├── Tests.tscn
│ └── src
│ ├── AutoloadCacheTest.cs
│ ├── extensions
│ ├── NodeExtensionsTest.cs
│ └── StringExtensionsTest.cs
│ ├── nodes
│ └── SchedulerTest.cs
│ ├── state
│ ├── MachineTest.cs
│ └── NotifierTest.cs
│ └── utils
│ └── RngTest.cs
├── Chickensoft.GoDotNet.sln
├── Chickensoft.GoDotNet
├── Chickensoft.GoDotNet.csproj
├── icon.png
└── src
│ ├── AutoloadCache.cs
│ ├── extensions
│ ├── NodeExtensions.cs
│ └── StringExtensions.cs
│ ├── nodes
│ └── Scheduler.cs
│ ├── state
│ ├── Machine.cs
│ └── Notifier.cs
│ └── utils
│ └── Rng.cs
├── LICENSE
├── README.md
├── cspell.json
├── docs
├── renovatebot_pr.png
└── spelling_fix.png
├── global.json
└── renovate.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | # --------------------------------------------------------------------------- #
2 | # Chickensoft C# Style — .editorconfig #
3 | # --------------------------------------------------------------------------- #
4 | # Godot-friendly, K&R coding style with a bit of Dart-style flair thrown in. #
5 | # --------------------------------------------------------------------------- #
6 | # #
7 | # #
8 | # ╓╗_▄╗_╓▄_ #
9 | # ▄▄╟▓▓▓▓▓▓▓▓ #
10 | # ╙▓▓▓▀▀╠╠╦╦╓,_ #
11 | # ,φ╠╠╠╠╠╠╠╠╠╠▒╥ #
12 | # φ╠╠╠╠╠╠╠╠╠╠╠╠╠╠╦ #
13 | # @╠╠╫▌╠╟▌╠╠╠╠╠╠╠╠╠ #
14 | # ╠╠╠▄▄▄▒╠╠╠╠╠╠╠╠╠╠b #
15 | # ╠╠╨███▌╠╠╠╠╠╠╠▒╠╠▒_ ç╓ #
16 | # ╠╠╠╠▒▒╠╠╠╠╠╠╠╠▒Å▄╠╬▒φ╩ε #
17 | # ╚╠╠╠╠╠╠╠╠╠╠╠▒█▒╫█Å╠╠╩ #
18 | # ╠╠╠╠╠╠╠╠╠╠╠╠╠╟╫▒╠╠╩ #
19 | # ╙╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╜ #
20 | # ╙╚╠╠╠╠╠╠╠╠╩╙ #
21 | # ╒ µ #
22 | # ▌ ▓ #
23 | # ^▀▀ "▀ª #
24 | # #
25 | # #
26 | # --------------------------------------------------------------------------- #
27 | #
28 | # Based on:
29 | # - https://github.com/RehanSaeed/EditorConfig/blob/main/.editorconfig
30 | # - https://gist.github.com/FaronBracy/155d8d7ad98b4ceeb526b9f47543db1b
31 | # - various other gists floating around :)
32 | #
33 | # Have a problem? Encounter an issue?
34 | # Come visit our Discord and let us know! https://discord.gg/MjA6HUzzAE
35 | #
36 | # Based on https://github.com/RehanSaeed/EditorConfig/blob/main/.editorconfig
37 | # and https://gist.github.com/FaronBracy/155d8d7ad98b4ceeb526b9f47543db1b
38 |
39 | # This file is the top-most EditorConfig file
40 | root = true
41 |
42 | # All Files
43 | [*]
44 | charset = utf-8
45 | indent_style = space
46 | indent_size = 2
47 | insert_final_newline = true
48 | trim_trailing_whitespace = true
49 |
50 | ##########################################
51 | # File Extension Settings
52 | ##########################################
53 |
54 | # Visual Studio Solution Files
55 | [*.sln]
56 | indent_style = tab
57 |
58 | # Visual Studio XML Project Files
59 | [*.{csproj,vbproj,vcxproj.filters,proj,projitems,shproj}]
60 | indent_size = 2
61 |
62 | # XML Configuration Files
63 | [*.{xml,config,props,targets,nuspec,resx,ruleset,vsixmanifest,vsct}]
64 | indent_size = 2
65 |
66 | # JSON Files
67 | [*.{json,json5,webmanifest}]
68 | indent_size = 2
69 |
70 | # YAML Files
71 | [*.{yml,yaml}]
72 | indent_size = 2
73 |
74 | # Markdown Files
75 | [*.{md,mdx}]
76 | trim_trailing_whitespace = false
77 |
78 | # Web Files
79 | [*.{htm,html,js,jsm,ts,tsx,cjs,cts,ctsx,mjs,mts,mtsx,css,sass,scss,less,pcss,svg,vue}]
80 | indent_size = 2
81 |
82 | # Batch Files
83 | [*.{cmd,bat}]
84 | end_of_line = crlf
85 |
86 | # Bash Files
87 | [*.sh]
88 | end_of_line = lf
89 |
90 | # Makefiles
91 | [Makefile]
92 | indent_style = tab
93 |
94 | [*_Generated.cs, *.g.cs, *.generated.cs]
95 | # Ignore a lack of documentation for generated code. Doesn't apply to builds,
96 | # just to viewing generation output.
97 | dotnet_diagnostic.CS1591.severity = none
98 |
99 | ##########################################
100 | # Default .NET Code Style Severities
101 | ##########################################
102 |
103 | [*.{cs,csx,cake,vb,vbx}]
104 | # Default Severity for all .NET Code Style rules below
105 | dotnet_analyzer_diagnostic.severity = warning
106 |
107 | ##########################################
108 | # Language Rules
109 | ##########################################
110 |
111 | # .NET Style Rules
112 | [*.{cs,csx,cake,vb,vbx}]
113 |
114 | # "this." and "Me." qualifiers
115 | dotnet_style_qualification_for_field = false
116 | dotnet_style_qualification_for_property = false
117 | dotnet_style_qualification_for_method = false
118 | dotnet_style_qualification_for_event = false
119 |
120 | # Language keywords instead of framework type names for type references
121 | dotnet_style_predefined_type_for_locals_parameters_members = true:warning
122 | dotnet_style_predefined_type_for_member_access = true:warning
123 |
124 | # Modifier preferences
125 | dotnet_style_require_accessibility_modifiers = always:warning
126 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:warning
127 | 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:warning
128 | dotnet_style_readonly_field = true:warning
129 |
130 | # Parentheses preferences
131 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:warning
132 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:warning
133 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:warning
134 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary:warning
135 |
136 | # Expression-level preferences
137 | dotnet_style_object_initializer = true:warning
138 | dotnet_style_collection_initializer = true:warning
139 | dotnet_style_explicit_tuple_names = true:warning
140 | dotnet_style_prefer_inferred_tuple_names = true:warning
141 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning
142 | dotnet_style_prefer_auto_properties = true:warning
143 | dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
144 | dotnet_diagnostic.IDE0045.severity = suggestion
145 | dotnet_style_prefer_conditional_expression_over_return = true:suggestion
146 | dotnet_diagnostic.IDE0046.severity = suggestion
147 | dotnet_style_prefer_compound_assignment = true:warning
148 | dotnet_style_prefer_simplified_interpolation = true:warning
149 | dotnet_style_prefer_simplified_boolean_expressions = true:warning
150 |
151 | # Null-checking preferences
152 | dotnet_style_coalesce_expression = true:warning
153 | dotnet_style_null_propagation = true:warning
154 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning
155 |
156 | # File header preferences
157 | # Keep operators at end of line when wrapping.
158 | dotnet_style_operator_placement_when_wrapping = end_of_line:warning
159 | csharp_style_prefer_null_check_over_type_check = true:warning
160 |
161 | # Code block preferences
162 | csharp_prefer_braces = true:warning
163 | csharp_prefer_simple_using_statement = true:suggestion
164 | dotnet_diagnostic.IDE0063.severity = suggestion
165 |
166 | # C# Style Rules
167 | [*.{cs,csx,cake}]
168 | # 'var' preferences
169 | csharp_style_var_for_built_in_types = true:warning
170 | csharp_style_var_when_type_is_apparent = true:warning
171 | csharp_style_var_elsewhere = true:warning
172 | # Expression-bodied members
173 | csharp_style_expression_bodied_methods = true:warning
174 | csharp_style_expression_bodied_constructors = false:warning
175 | csharp_style_expression_bodied_operators = true:warning
176 | csharp_style_expression_bodied_properties = true:warning
177 | csharp_style_expression_bodied_indexers = true:warning
178 | csharp_style_expression_bodied_accessors = true:warning
179 | csharp_style_expression_bodied_lambdas = true:warning
180 | csharp_style_expression_bodied_local_functions = true:warning
181 | # Pattern matching preferences
182 | csharp_style_pattern_matching_over_is_with_cast_check = true:warning
183 | csharp_style_pattern_matching_over_as_with_null_check = true:warning
184 | csharp_style_prefer_switch_expression = true:warning
185 | csharp_style_prefer_pattern_matching = true:warning
186 | csharp_style_prefer_not_pattern = true:warning
187 | # Expression-level preferences
188 | csharp_style_inlined_variable_declaration = true:warning
189 | csharp_prefer_simple_default_expression = true:warning
190 | csharp_style_pattern_local_over_anonymous_function = true:warning
191 | csharp_style_deconstructed_variable_declaration = true:warning
192 | csharp_style_prefer_index_operator = true:warning
193 | csharp_style_prefer_range_operator = true:warning
194 | csharp_style_implicit_object_creation_when_type_is_apparent = true:warning
195 | # "Null" checking preferences
196 | csharp_style_throw_expression = true:warning
197 | csharp_style_conditional_delegate_call = true:warning
198 | # Code block preferences
199 | csharp_prefer_braces = true:warning
200 | csharp_prefer_simple_using_statement = true:suggestion
201 | dotnet_diagnostic.IDE0063.severity = suggestion
202 | # 'using' directive preferences
203 | csharp_using_directive_placement = inside_namespace:warning
204 | # Modifier preferences
205 | # Don't suggest making public methods static. Very annoying.
206 | csharp_prefer_static_local_function = false
207 | # Only suggest making private methods static (if they don't use instance data).
208 | dotnet_code_quality.CA1822.api_surface = private
209 |
210 | ##########################################
211 | # Unnecessary Code Rules
212 | ##########################################
213 |
214 | # .NET Unnecessary code rules
215 | [*.{cs,csx,cake,vb,vbx}]
216 |
217 | dotnet_code_quality_unused_parameters = non_public:suggestion
218 | dotnet_remove_unnecessary_suppression_exclusions = none
219 | dotnet_diagnostic.IDE0079.severity = warning
220 |
221 | # C# Unnecessary code rules
222 | [*.{cs,csx,cake}]
223 |
224 | # Chickensoft Unused Code Additions
225 | #
226 | # Unfortunately for VSCode users, disabling these rules prevents you from
227 | # detecting unused code. Enabling them will trigger the roslyn analyzers'
228 | # automatic code fixes to remove unused code, which is very annoying when
229 | # you're actively coding or using reflection.
230 | #
231 | # I have not found a way to disable automatic fixes while keeping
232 | # warnings/suggestions/etc in the editor. If you find a way, please file an
233 | # issue or open a PR.
234 |
235 | # Don't remove method parameters that are unused.
236 | dotnet_diagnostic.IDE0060.severity = none
237 | dotnet_diagnostic.RCS1163.severity = none
238 |
239 | # Don't remove methods that are unused.
240 | dotnet_diagnostic.IDE0051.severity = none
241 | dotnet_diagnostic.RCS1213.severity = none
242 |
243 | # Use discard variable for unused expression values.
244 | csharp_style_unused_value_expression_statement_preference = discard_variable
245 |
246 | # .NET formatting rules
247 | [*.{cs,csx,cake,vb,vbx}]
248 |
249 | # Organize using directives
250 | dotnet_sort_system_directives_first = true
251 | dotnet_separate_import_directive_groups = false
252 |
253 | # Dotnet namespace options
254 | #
255 | # We don't care about namespaces matching folder structure. Games and apps
256 | # are complicated and you are free to organize them however you like. Change
257 | # this if you want to enforce it.
258 | dotnet_style_namespace_match_folder = false
259 | dotnet_diagnostic.IDE0130.severity = none
260 |
261 | # C# formatting rules
262 | [*.{cs,csx,cake}]
263 |
264 | # Newline options
265 | csharp_new_line_before_open_brace = none
266 | csharp_new_line_before_else = true
267 | csharp_new_line_before_catch = true
268 | csharp_new_line_before_finally = true
269 | csharp_new_line_before_members_in_object_initializers = true
270 | csharp_new_line_before_members_in_anonymous_types = true
271 | csharp_new_line_between_query_expression_clauses = true
272 |
273 | # Indentation options
274 | csharp_indent_switch_labels = true
275 | csharp_indent_case_contents = true
276 | csharp_indent_case_contents_when_block = true
277 | csharp_indent_labels = no_change
278 | csharp_indent_block_contents = true
279 | csharp_indent_braces = false
280 |
281 | # Spacing options
282 | csharp_space_after_cast = false
283 | csharp_space_after_keywords_in_control_flow_statements = true
284 | csharp_space_between_parentheses = false
285 | csharp_space_before_colon_in_inheritance_clause = true
286 | csharp_space_after_colon_in_inheritance_clause = true
287 | csharp_space_around_binary_operators = before_and_after
288 | csharp_space_between_method_declaration_parameter_list_parentheses = false
289 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
290 | csharp_space_between_method_declaration_name_and_open_parenthesis = false
291 | csharp_space_between_method_call_parameter_list_parentheses = false
292 | csharp_space_between_method_call_empty_parameter_list_parentheses = false
293 | csharp_space_between_method_call_name_and_opening_parenthesis = false
294 | csharp_space_after_comma = true
295 | csharp_space_before_comma = false
296 | csharp_space_after_dot = false
297 | csharp_space_before_dot = false
298 | csharp_space_after_semicolon_in_for_statement = true
299 | csharp_space_before_semicolon_in_for_statement = false
300 | csharp_space_around_declaration_statements = false
301 | csharp_space_before_open_square_brackets = false
302 | csharp_space_between_empty_square_brackets = false
303 | csharp_space_between_square_brackets = false
304 |
305 | # Wrap options
306 | csharp_preserve_single_line_statements = false
307 | csharp_preserve_single_line_blocks = true
308 |
309 | # Namespace options
310 | csharp_style_namespace_declarations = file_scoped:warning
311 |
312 | ##########################################
313 | # .NET Naming Rules
314 | ##########################################
315 | [*.{cs,csx,cake,vb,vbx}]
316 |
317 | ##########################################
318 | # Chickensoft Naming Conventions & Styles
319 | # These deviate heavily from Microsoft's Offical Naming Conventions.
320 | ##########################################
321 |
322 | # Allow underscores in names.
323 | dotnet_diagnostic.CA1707.severity = none
324 |
325 | # Styles
326 | dotnet_naming_style.pascal_case_style.capitalization = pascal_case
327 |
328 | dotnet_naming_style.upper_case_style.capitalization = all_upper
329 | dotnet_naming_style.upper_case_style.word_separator = _
330 |
331 | dotnet_naming_style.camel_case_style.capitalization = camel_case
332 |
333 | dotnet_naming_style.camel_case_underscore_style.required_prefix = _
334 | dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case
335 |
336 | # Use uppercase for all constant fields.
337 | dotnet_naming_rule.constants_uppercase.severity = suggestion
338 | dotnet_naming_rule.constants_uppercase.symbols = constant_fields
339 | dotnet_naming_rule.constants_uppercase.style = upper_case_style
340 | dotnet_naming_symbols.constant_fields.applicable_kinds = field
341 | dotnet_naming_symbols.constant_fields.applicable_accessibilities = *
342 | dotnet_naming_symbols.constant_fields.required_modifiers = const
343 |
344 | # Non-public fields should be _camelCase
345 | dotnet_naming_rule.non_public_fields_under_camel.severity = suggestion
346 | dotnet_naming_rule.non_public_fields_under_camel.symbols = non_public_fields
347 | dotnet_naming_rule.non_public_fields_under_camel.style = camel_case_underscore_style
348 | dotnet_naming_symbols.non_public_fields.applicable_kinds = field
349 | dotnet_naming_symbols.non_public_fields.required_modifiers =
350 | dotnet_naming_symbols.non_public_fields.applicable_accessibilities = private,private_protected,protected,internal,protected,protected_internal
351 |
352 | # Public fields should be PascalCase
353 | dotnet_naming_rule.public_fields_pascal.severity = suggestion
354 | dotnet_naming_rule.public_fields_pascal.symbols = public_fields
355 | dotnet_naming_rule.public_fields_pascal.style = pascal_case_style
356 | dotnet_naming_symbols.public_fields.applicable_kinds = field
357 | dotnet_naming_symbols.public_fields.required_modifiers =
358 | dotnet_naming_symbols.public_fields.applicable_accessibilities = public
359 |
360 | # Async methods should have "Async" suffix.
361 | # Disabled because it makes tests too verbose.
362 | # dotnet_naming_style.end_in_async.required_suffix = Async
363 | # dotnet_naming_style.end_in_async.capitalization = pascal_case
364 | # dotnet_naming_rule.methods_end_in_async.symbols = methods_async
365 | # dotnet_naming_rule.methods_end_in_async.style = end_in_async
366 | # dotnet_naming_rule.methods_end_in_async.severity = warning
367 | # dotnet_naming_symbols.methods_async.applicable_kinds = method
368 | # dotnet_naming_symbols.methods_async.required_modifiers = async
369 | # dotnet_naming_symbols.methods_async.applicable_accessibilities = *
370 |
371 | ##########################################
372 | # Other Naming Rules
373 | ##########################################
374 |
375 | # All of the following must be PascalCase:
376 | dotnet_naming_symbols.element_group.applicable_kinds = namespace, class, enum, struct, delegate, event, method, property
377 | dotnet_naming_rule.element_rule.symbols = element_group
378 | dotnet_naming_rule.element_rule.style = pascal_case_style
379 | dotnet_naming_rule.element_rule.severity = warning
380 |
381 | # Interfaces use PascalCase and are prefixed with uppercase 'I'
382 | # https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces
383 | dotnet_naming_style.prefix_interface_with_i_style.capitalization = pascal_case
384 | dotnet_naming_style.prefix_interface_with_i_style.required_prefix = I
385 | dotnet_naming_symbols.interface_group.applicable_kinds = interface
386 | dotnet_naming_rule.interface_rule.symbols = interface_group
387 | dotnet_naming_rule.interface_rule.style = prefix_interface_with_i_style
388 | dotnet_naming_rule.interface_rule.severity = warning
389 |
390 | # Generics Type Parameters use PascalCase and are prefixed with uppercase 'T'
391 | # https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces
392 | dotnet_naming_style.prefix_type_parameters_with_t_style.capitalization = pascal_case
393 | dotnet_naming_style.prefix_type_parameters_with_t_style.required_prefix = T
394 | dotnet_naming_symbols.type_parameter_group.applicable_kinds = type_parameter
395 | dotnet_naming_rule.type_parameter_rule.symbols = type_parameter_group
396 | dotnet_naming_rule.type_parameter_rule.style = prefix_type_parameters_with_t_style
397 | dotnet_naming_rule.type_parameter_rule.severity = warning
398 |
399 | # Function parameters use camelCase
400 | # https://docs.microsoft.com/dotnet/standard/design-guidelines/naming-parameters
401 | dotnet_naming_symbols.parameters_group.applicable_kinds = parameter
402 | dotnet_naming_rule.parameters_rule.symbols = parameters_group
403 | dotnet_naming_rule.parameters_rule.style = camel_case_style
404 | dotnet_naming_rule.parameters_rule.severity = warning
405 |
406 | # Anything not specified uses camel case.
407 | dotnet_naming_rule.unspecified_naming.severity = warning
408 | dotnet_naming_rule.unspecified_naming.symbols = unspecified
409 | dotnet_naming_rule.unspecified_naming.style = camel_case_style
410 | dotnet_naming_symbols.unspecified.applicable_kinds = *
411 | dotnet_naming_symbols.unspecified.applicable_accessibilities = *
412 |
413 | ##########################################
414 | # Chickensoft Rule Overrides
415 | ##########################################
416 |
417 | # Allow using keywords as names
418 | # dotnet_diagnostic.CA1716.severity = none
419 | # Don't require culture info for ToString()
420 | dotnet_diagnostic.CA1304.severity = none
421 | # Don't require a string comparison for comparing strings.
422 | dotnet_diagnostic.CA1310.severity = none
423 | # Don't require a string format specifier.
424 | dotnet_diagnostic.CA1305.severity = none
425 | # Allow protected fields.
426 | dotnet_diagnostic.CA1051.severity = none
427 | # Don't warn about checking values that are supposedly never null. Sometimes
428 | # they are actually null.
429 | dotnet_diagnostic.CS8073.severity = none
430 | # Don't remove seemingly "unnecessary" assignments, as they often have
431 | # intended side-effects.
432 | dotnet_diagnostic.IDE0059.severity = none
433 | # Switch/case should always have a default clause. Tell that to Roslynator.
434 | dotnet_diagnostic.RCS1070.severity = none
435 | # Tell roslynator not to eat unused parameters.
436 | dotnet_diagnostic.RCS1163.severity = none
437 | # Tell dotnet not to remove unused parameters.
438 | dotnet_diagnostic.IDE0060.severity = none
439 | # Tell roslynator not to remove `partial` modifiers.
440 | dotnet_diagnostic.RCS1043.severity = none
441 | # Tell roslynator not to make classes static so aggressively.
442 | dotnet_diagnostic.RCS1102.severity = none
443 | # Roslynator wants to make properties readonly all the time, so stop it.
444 | # The developer knows best when it comes to contract definitions with Godot.
445 | dotnet_diagnostic.RCS1170.severity = none
446 | # Allow expression values to go unused, even without discard variable.
447 | # Otherwise, using Moq would be way too verbose.
448 | dotnet_diagnostic.IDE0058.severity = none
449 | # Don't let roslynator turn every local variable into a const.
450 | # If we did, we'd have to specify the types of local variables far more often,
451 | # and this style prefers type inference.
452 | dotnet_diagnostic.RCS1118.severity = none
453 | # Enums don't need to declare explicit values. Everyone knows they start at 0.
454 | dotnet_diagnostic.RCS1161.severity = none
455 | # Allow unconstrained type parameter to be checked for null.
456 | dotnet_diagnostic.RCS1165.severity = none
457 | # Allow keyword-based names so that parameter names like `@event` can be used.
458 | dotnet_diagnostic.CA1716.severity = none
459 | # Allow exceptions without the standard exception constructors.
460 | dotnet_diagnostic.RCS1194.severity = none
461 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
2 | *.png filter=lfs diff=lfs merge=lfs -text
3 |
--------------------------------------------------------------------------------
/.github/workflows/auto_release.yaml:
--------------------------------------------------------------------------------
1 | # This workflow will run whenever tests finish running. If tests pass, it will
2 | # look at the last commit message to see if it contains the phrase
3 | # "chore(deps): update all dependencies".
4 | #
5 | # If it finds a commit with that phrase, and the testing workflow has passed,
6 | # it will automatically release a new version of the project by running the
7 | # publish workflow.
8 | #
9 | # The commit message phrase above is always used by renovatebot when opening
10 | # PR's to update dependencies. If you have renovatebot enabled and set to
11 | # automatically merge in dependency updates, this can automatically release and
12 | # publish the updated version of the project.
13 | #
14 | # You can disable this action by setting the DISABLE_AUTO_RELEASE repository
15 | # variable to true.
16 |
17 | name: 🦾 Auto-Release
18 | on:
19 | workflow_run:
20 | workflows: ["🚥 Tests"]
21 | branches:
22 | - main
23 | types:
24 | - completed
25 |
26 | jobs:
27 | auto_release:
28 | name: 🦾 Auto-Release
29 | runs-on: ubuntu-latest
30 | outputs:
31 | should_release: ${{ steps.release.outputs.should_release }}
32 | steps:
33 | - name: 🧾 Checkout
34 | uses: actions/checkout@v3
35 |
36 | - name: 🧑🔬 Check Test Results
37 | id: tests
38 | run: |
39 | echo "passed=${{ github.event.workflow_run.conclusion == 'success' }}" >> "$GITHUB_OUTPUT"
40 |
41 | - name: 📄 Check If Dependencies Changed
42 | id: deps
43 | run: |
44 | message=$(git log -1 --pretty=%B)
45 |
46 | if [[ $message == *"chore(deps): update all dependencies"* ]]; then
47 | echo "changed=true" >> "$GITHUB_OUTPUT"
48 | else
49 | echo "changed=false" >> "$GITHUB_OUTPUT"
50 | fi
51 |
52 | - name: 📝 Check Release Status
53 | id: release
54 | run: |
55 | echo "Tests passed: ${{ steps.tests.outputs.passed }}"
56 | echo "Dependencies changed: ${{ steps.deps.outputs.changed }}"
57 | disable_auto_release='${{ vars.DISABLE_AUTO_RELEASE }}'
58 | echo "DISABLE_AUTO_RELEASE=$disable_auto_release"
59 |
60 | if [[ ${{ steps.tests.outputs.passed }} == "true" && ${{ steps.deps.outputs.changed }} == "true" && $disable_auto_release != "true" ]]; then
61 | echo "should_release=true" >> "$GITHUB_OUTPUT"
62 | echo "🦾 Creating a release!"
63 | else
64 | echo "should_release=false" >> "$GITHUB_OUTPUT"
65 | echo "✋ Not creating a release."
66 | fi
67 |
68 | release:
69 | uses: './.github/workflows/publish.yaml'
70 | needs: auto_release
71 | if: needs.auto_release.outputs.should_release == 'true'
72 | secrets:
73 | NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
74 | with:
75 | bump: patch
76 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yaml:
--------------------------------------------------------------------------------
1 | name: '📦 Publish'
2 | on:
3 | workflow_dispatch:
4 | branches:
5 | - main
6 | inputs:
7 | bump:
8 | description: "Version Bump Method"
9 | type: choice
10 | options:
11 | - major
12 | - minor
13 | - patch
14 | required: true
15 | workflow_call:
16 | secrets:
17 | NUGET_API_KEY:
18 | description: "NuGet API Key"
19 | required: true
20 | inputs:
21 | bump:
22 | description: "Version Bump Method"
23 | type: string
24 | required: true
25 |
26 | jobs:
27 | publish:
28 | name: 📦 Publish
29 | runs-on: ubuntu-latest
30 | steps:
31 | - name: 🧾 Checkout
32 | uses: actions/checkout@v3
33 | with:
34 | lfs: true
35 | submodules: 'recursive'
36 |
37 | - name: 🔎 Read Current Project Verson
38 | uses: KageKirin/get-csproj-version@v1.0.0
39 | id: current-version
40 | with:
41 | file: Chickensoft.GoDotNet/Chickensoft.GoDotNet.csproj
42 | xpath: /Project/PropertyGroup/Version
43 |
44 | - name: 🖨 Print Current Version
45 | run: |
46 | echo "Current Version: ${{ steps.current-version.outputs.version }}"
47 |
48 | - name: 🧮 Compute Next Version
49 | uses: chickensoft-games/next-godot-csproj-version@v1
50 | id: next-version
51 | with:
52 | project-version: ${{ steps.current-version.outputs.version }}
53 | godot-version: global.json
54 | bump: ${{ inputs.bump }}
55 |
56 | - name: ✨ Print Next Version
57 | run: |
58 | echo "Next Version: ${{ steps.next-version.outputs.version }}"
59 |
60 | - name: 📝 Change Version
61 | uses: vers-one/dotnet-project-version-updater@v1.3
62 | with:
63 | file: "Chickensoft.GoDotNet/Chickensoft.GoDotNet.csproj"
64 | version: ${{ steps.next-version.outputs.version }}
65 |
66 | - name: ✍️ Commit Changes
67 | run: |
68 | git config user.name "action@github.com"
69 | git config user.email "GitHub Action"
70 | git commit -a -m "chore(version): update version to ${{ steps.next-version.outputs.version }}"
71 | git push
72 |
73 | - name: ✨ Create Release
74 | env:
75 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
76 | run: gh release create --generate-notes "v${{ steps.next-version.outputs.version }}"
77 |
78 | - name: 💽 Setup .NET SDK
79 | uses: actions/setup-dotnet@v3
80 | with:
81 | # Use the .NET SDK from global.json in the root of the repository.
82 | global-json-file: global.json
83 |
84 | - name: 📦 Publish
85 | run: |
86 | # build the package
87 | dotnet build Chickensoft.GoDotNet/Chickensoft.GoDotNet.csproj -c Release
88 |
89 | # find the built nuget package
90 | nuget_package=$(find . -name "Chickensoft.GoDotNet.*.nupkg")
91 |
92 | echo "📦 Publishing package: $nuget_package"
93 |
94 | # publish the nuget package
95 | dotnet nuget push "$nuget_package" --api-key "${{ secrets.NUGET_API_KEY }}" --source "https://api.nuget.org/v3/index.json" --skip-duplicate
96 |
--------------------------------------------------------------------------------
/.github/workflows/spellcheck.yaml:
--------------------------------------------------------------------------------
1 | name: '🧑🏫 Spellcheck'
2 | on:
3 | push:
4 | pull_request:
5 |
6 | jobs:
7 | spellcheck:
8 | name: '🧑🏫 Spellcheck'
9 | # Only run the workflow if it's not a PR or if it's a PR from a fork.
10 | # This prevents duplicate workflows from running on PR's that originate
11 | # from the repository itself.
12 | if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
13 | runs-on: ubuntu-latest
14 | defaults:
15 | run:
16 | working-directory: '.'
17 | steps:
18 | - uses: actions/checkout@v3
19 | name: 🧾 Checkout
20 |
21 | - uses: streetsidesoftware/cspell-action@v3
22 | name: 📝 Check Spelling
23 | with:
24 | config: './cspell.json'
25 | incremental_files_only: false
26 | root: '.'
27 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yaml:
--------------------------------------------------------------------------------
1 | name: 🚥 Tests
2 | on:
3 | push:
4 | pull_request:
5 |
6 | jobs:
7 | tests:
8 | name: 🧪 Evaluate Tests on ${{ matrix.os }}
9 | # Only run the workflow if it's not a PR or if it's a PR from a fork.
10 | # This prevents duplicate workflows from running on PR's that originate
11 | # from the repository itself.
12 | if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
13 | runs-on: ${{ matrix.os }}
14 | strategy:
15 | # Don't cancel other OS runners if one fails.
16 | fail-fast: false
17 | matrix:
18 | # Put the operating systems you want to run on here.
19 | #
20 | # You can change windows-2019 to windows-latest, but windows-2019
21 | # was running in half the time. Try it out and see what works best.
22 | os: [ubuntu-latest, macos-latest, windows-2019]
23 | env:
24 | DOTNET_CLI_TELEMETRY_OPTOUT: true
25 | DOTNET_NOLOGO: true
26 | defaults:
27 | run:
28 | # Use bash shells on all platforms.
29 | shell: bash
30 | steps:
31 | - name: 🧾 Checkout
32 | uses: actions/checkout@v3
33 |
34 | - name: 💽 Setup .NET SDK
35 | uses: actions/setup-dotnet@v3
36 | with:
37 | # Use the .NET SDK from global.json in the root of the repository.
38 | global-json-file: global.json
39 |
40 | - name: 📦 Restore Dependencies
41 | run: dotnet restore
42 |
43 | - name: 🤖 Setup Godot
44 | uses: chickensoft-games/setup-godot@v1
45 | with:
46 | # Version must include major, minor, and patch, and be >= 4.0.0
47 | # Pre-release label is optional.
48 | #
49 | # In this case, we are using the version from global.json.
50 | #
51 | # This allows checks on renovatebot PR's to succeed whenever
52 | # renovatebot updates the Godot SDK version.
53 | version: global.json
54 |
55 | - name: 🔬 Verify Setup
56 | run: |
57 | dotnet --version
58 | godot --version
59 |
60 | - name: 🧑🔬 Generate .NET Bindings
61 | working-directory: Chickensoft.GoDotNet.Tests
62 | run: godot --headless --build-solutions --quit || exit 0
63 |
64 | - name: 🦺 Build Projects
65 | run: dotnet build
66 |
67 | - name: 🧪 Run Tests
68 | working-directory: Chickensoft.GoDotNet.Tests
69 | run: godot --headless --run-tests --quit-on-finish
70 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | Chickensoft.GoDotNet/nupkg/
2 |
3 | Chickensoft.GoDotNet.Tests/coverage/*
4 | !Chickensoft.GoDotNet.Tests/coverage/.gdignore
5 |
6 | .godot/
7 | bin/
8 | obj/
9 | .generated/
10 | .vs/
11 | .DS_Store
12 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "ms-dotnettools.csharp",
4 | "selcukermaya.se-csproj-extensions",
5 | "josefpihrt-vscode.roslynator",
6 | "streetsidesoftware.code-spell-checker",
7 | "VisualStudioExptTeam.vscodeintellicode",
8 | "DavidAnson.vscode-markdownlint"
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | // For these launch configurations to work, you need to setup a GODOT4
5 | // environment variable. On mac or linux, this can be done by adding
6 | // the following to your .zshrc, .bashrc, or .bash_profile file:
7 | // export GODOT4="/Applications/Godot.app/Contents/MacOS/Godot"
8 | {
9 | "name": "🧪 Debug Tests",
10 | "type": "coreclr",
11 | "request": "launch",
12 | "preLaunchTask": "build",
13 | "program": "${env:GODOT4}",
14 | "args": [
15 | // These command line flags are used by GoDotTest to run tests.
16 | "--run-tests",
17 | "--quit-on-finish"
18 | ],
19 | "cwd": "${workspaceFolder}/Chickensoft.GoDotNet.Tests",
20 | "stopAtEntry": false,
21 | },
22 | {
23 | "name": "🔬 Debug Current Test",
24 | "type": "coreclr",
25 | "request": "launch",
26 | "preLaunchTask": "build",
27 | "program": "${env:GODOT4}",
28 | "args": [
29 | // These command line flags are used by GoDotTest to run tests.
30 | "--run-tests=${fileBasenameNoExtension}",
31 | "--quit-on-finish"
32 | ],
33 | "cwd": "${workspaceFolder}/Chickensoft.GoDotNet.Tests",
34 | "stopAtEntry": false,
35 | },
36 | ]
37 | }
38 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "[csharp]": {
3 | "editor.codeActionsOnSave": {
4 | "source.addMissingImports": true,
5 | "source.fixAll": true,
6 | "source.organizeImports": true
7 | },
8 | "editor.formatOnPaste": true,
9 | "editor.formatOnSave": true,
10 | "editor.formatOnType": false
11 | },
12 | "csharp.semanticHighlighting.enabled": true,
13 | "editor.semanticHighlighting.enabled": true,
14 | // C# doc comment colorization gets lost with semantic highlighting, but we
15 | // need semantic highlighting for proper syntax highlighting with record
16 | // shorthand.
17 | //
18 | // Here's a workaround for doc comment highlighting from
19 | // https://github.com/OmniSharp/omnisharp-vscode/issues/3816
20 | "editor.tokenColorCustomizations": {
21 | "[*Dark*]": {
22 | // Themes that include the word "Dark" in them.
23 | "textMateRules": [
24 | {
25 | "scope": "comment.documentation",
26 | "settings": {
27 | "foreground": "#608B4E"
28 | }
29 | },
30 | {
31 | "scope": "comment.documentation.attribute",
32 | "settings": {
33 | "foreground": "#C8C8C8"
34 | }
35 | },
36 | {
37 | "scope": "comment.documentation.cdata",
38 | "settings": {
39 | "foreground": "#E9D585"
40 | }
41 | },
42 | {
43 | "scope": "comment.documentation.delimiter",
44 | "settings": {
45 | "foreground": "#808080"
46 | }
47 | },
48 | {
49 | "scope": "comment.documentation.name",
50 | "settings": {
51 | "foreground": "#569CD6"
52 | }
53 | }
54 | ]
55 | },
56 | "[*Light*]": {
57 | // Themes that include the word "Light" in them.
58 | "textMateRules": [
59 | {
60 | "scope": "comment.documentation",
61 | "settings": {
62 | "foreground": "#008000"
63 | }
64 | },
65 | {
66 | "scope": "comment.documentation.attribute",
67 | "settings": {
68 | "foreground": "#282828"
69 | }
70 | },
71 | {
72 | "scope": "comment.documentation.cdata",
73 | "settings": {
74 | "foreground": "#808080"
75 | }
76 | },
77 | {
78 | "scope": "comment.documentation.delimiter",
79 | "settings": {
80 | "foreground": "#808080"
81 | }
82 | },
83 | {
84 | "scope": "comment.documentation.name",
85 | "settings": {
86 | "foreground": "#808080"
87 | }
88 | }
89 | ]
90 | },
91 | "[*]": {
92 | // Themes that don't include the word "Dark" or "Light" in them.
93 | // These are some bold colors that show up well against most dark and
94 | // light themes.
95 | //
96 | // Change them to something that goes well with your preferred theme :)
97 | "textMateRules": [
98 | {
99 | "scope": "comment.documentation",
100 | "settings": {
101 | "foreground": "#0091ff"
102 | }
103 | },
104 | {
105 | "scope": "comment.documentation.attribute",
106 | "settings": {
107 | "foreground": "#8480ff"
108 | }
109 | },
110 | {
111 | "scope": "comment.documentation.cdata",
112 | "settings": {
113 | "foreground": "#0091ff"
114 | }
115 | },
116 | {
117 | "scope": "comment.documentation.delimiter",
118 | "settings": {
119 | "foreground": "#aa00ff"
120 | }
121 | },
122 | {
123 | "scope": "comment.documentation.name",
124 | "settings": {
125 | "foreground": "#ef0074"
126 | }
127 | }
128 | ]
129 | }
130 | },
131 | "markdownlint.config": {
132 | // Allow html in markdown.
133 | "MD033": false,
134 | // Allow non-unique heading names so we don't break the changelog.
135 | "MD024": false
136 | },
137 | "markdownlint.ignore": [
138 | "**/LICENSE"
139 | ],
140 | "omnisharp.enableEditorConfigSupport": true,
141 | "omnisharp.enableMsBuildLoadProjectsOnDemand": false,
142 | "omnisharp.enableRoslynAnalyzers": true,
143 | "omnisharp.maxFindSymbolsItems": 3000,
144 | "omnisharp.organizeImportsOnFormat": true,
145 | "omnisharp.useModernNet": true,
146 | // Remove these if you're happy with your terminal profiles.
147 | "terminal.integrated.defaultProfile.windows": "Git Bash",
148 | "terminal.integrated.profiles.windows": {
149 | "Command Prompt": {
150 | "icon": "terminal-cmd",
151 | "path": [
152 | "${env:windir}\\Sysnative\\cmd.exe",
153 | "${env:windir}\\System32\\cmd.exe"
154 | ]
155 | },
156 | "Git Bash": {
157 | "icon": "terminal",
158 | "source": "Git Bash"
159 | },
160 | "PowerShell": {
161 | "icon": "terminal-powershell",
162 | "source": "PowerShell"
163 | }
164 | },
165 | "dotnet.completion.showCompletionItemsFromUnimportedNamespaces": true
166 | }
167 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "build",
6 | "command": "dotnet",
7 | "type": "process",
8 | "args": [
9 | "build",
10 | "--no-restore"
11 | ],
12 | "problemMatcher": "$msCompile",
13 | "presentation": {
14 | "echo": true,
15 | "reveal": "silent",
16 | "focus": false,
17 | "panel": "shared",
18 | "showReuseMessage": false,
19 | "clear": false
20 | }
21 | },
22 | {
23 | "label": "coverage",
24 | "group": "test",
25 | "command": "${workspaceFolder}/Chickensoft.GoDotNet.Tests/coverage.sh",
26 | "type": "shell",
27 | "options": {
28 | "cwd": "${workspaceFolder}/Chickensoft.GoDotNet.Tests"
29 | },
30 | "presentation": {
31 | "echo": true,
32 | "reveal": "always",
33 | "focus": false,
34 | "panel": "shared",
35 | "showReuseMessage": false,
36 | "clear": true
37 | },
38 | },
39 | {
40 | "label": "build-solutions",
41 | "group": "test",
42 | "command": "dotnet restore; ${env:GODOT4} --headless --build-solutions --quit || exit 0",
43 | "type": "shell",
44 | "options": {
45 | "cwd": "${workspaceFolder}/Chickensoft.GoDotNet.Tests"
46 | },
47 | "presentation": {
48 | "echo": true,
49 | "reveal": "silent",
50 | "focus": false,
51 | "panel": "shared",
52 | "showReuseMessage": false,
53 | "clear": false
54 | }
55 | },
56 | ]
57 | }
58 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Thank you for taking the time to read this contributing guide and for showing interest in helping this project!
4 |
5 | ## Getting Started
6 |
7 | Need a helping hand to get started? Check out these resources!
8 |
9 | - [Discord Server][discord]
10 | - [Chickensoft Website][chickensoft]
11 |
12 | Please read our [code of conduct](#code-of-conduct). We do our best to treat others fairly and foster a welcoming environment.
13 |
14 | ## Project Setup
15 |
16 | This is a C# nuget package, for use with the .NET SDK 6 or 7. As such, the `dotnet` tool will allow you to restore packages and build projects.
17 |
18 | The `Chickensoft.GoDotNet.Tests` project must be built with the Godot editor at least once before `dotnet build` will succeed. Godot has to generate the .NET bindings for the project, since tests run in an actual game environment.
19 |
20 | ## Coding Guidelines
21 |
22 | Your IDE should automatically adhere to the style guidelines in the provided `.editorconfig` file. Please try to keep lines under 80 characters long whenever possible.
23 |
24 | We try to write tests for our projects to ensure a certain level of quality. We are willing to give you support and guidance if you need help!
25 |
26 | ## Code of Conduct
27 |
28 | We follow the [Contributor Covenant][covenant].
29 |
30 | In short:
31 |
32 | > We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
33 |
34 |
35 |
36 | [discord]: https://discord.gg/gSjaPgMmYW
37 | [chickensoft]: https://chickensoft.games
38 | [covenant]: https://www.contributor-covenant.org/version/2/1/code_of_conduct/
39 |
--------------------------------------------------------------------------------
/Chickensoft.GoDotNet.Tests/Chickensoft.GoDotNet.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net6.0
4 | true
5 | 10.0
6 | enable
7 | Chickensoft.GoDotNet.Tests
8 |
9 |
10 | true
11 |
12 |
18 | full
19 | true
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/Chickensoft.GoDotNet.Tests/Chickensoft.GoDotNet.Tests.sln:
--------------------------------------------------------------------------------
1 | Microsoft Visual Studio Solution File, Format Version 12.00
2 | # Visual Studio 2012
3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Chickensoft.GoDotNet.Tests", "Chickensoft.GoDotNet.Tests.csproj", "{07C3BA9A-CC0E-47EC-996F-91C58DA3E779}"
4 | EndProject
5 | Global
6 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
7 | Debug|Any CPU = Debug|Any CPU
8 | ExportDebug|Any CPU = ExportDebug|Any CPU
9 | ExportRelease|Any CPU = ExportRelease|Any CPU
10 | EndGlobalSection
11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
12 | {07C3BA9A-CC0E-47EC-996F-91C58DA3E779}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
13 | {07C3BA9A-CC0E-47EC-996F-91C58DA3E779}.Debug|Any CPU.Build.0 = Debug|Any CPU
14 | {07C3BA9A-CC0E-47EC-996F-91C58DA3E779}.ExportDebug|Any CPU.ActiveCfg = ExportDebug|Any CPU
15 | {07C3BA9A-CC0E-47EC-996F-91C58DA3E779}.ExportDebug|Any CPU.Build.0 = ExportDebug|Any CPU
16 | {07C3BA9A-CC0E-47EC-996F-91C58DA3E779}.ExportRelease|Any CPU.ActiveCfg = ExportRelease|Any CPU
17 | {07C3BA9A-CC0E-47EC-996F-91C58DA3E779}.ExportRelease|Any CPU.Build.0 = ExportRelease|Any CPU
18 | EndGlobalSection
19 | EndGlobal
20 |
--------------------------------------------------------------------------------
/Chickensoft.GoDotNet.Tests/badges/.gdignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chickensoft-games/GoDotNet/29e0f78466c8295b028307605ba91c2ec01a7eec/Chickensoft.GoDotNet.Tests/badges/.gdignore
--------------------------------------------------------------------------------
/Chickensoft.GoDotNet.Tests/badges/branch_coverage.svg:
--------------------------------------------------------------------------------
1 |
2 |
48 | Code coverage
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 | Generated by: ReportGenerator 5.1.17.0
98 |
99 |
100 |
101 | Coverage
102 | Coverage
103 |
104 | 100% 100%
105 |
106 |
107 |
108 |
109 |
110 | Branch coverage
111 |
112 |
113 |
--------------------------------------------------------------------------------
/Chickensoft.GoDotNet.Tests/badges/line_coverage.svg:
--------------------------------------------------------------------------------
1 |
2 |
48 | Code coverage
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 | Generated by: ReportGenerator 5.1.17.0
98 |
99 |
100 |
101 | Coverage
102 | Coverage
103 | 100% 100%
104 |
105 |
106 |
107 |
108 |
109 | Line coverage
110 |
111 |
112 |
113 |
--------------------------------------------------------------------------------
/Chickensoft.GoDotNet.Tests/coverage.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # To collect code coverage, you will need the following environment setup:
4 | #
5 | # - A "GODOT4" environment variable pointing to the Godot executable
6 | # - ReportGenerator installed
7 | #
8 | # dotnet tool install -g dotnet-reportgenerator-globaltool
9 | #
10 | # - A version of coverlet > 3.2.0.
11 | #
12 | # As of Jan 2023, this is not yet released.
13 | #
14 | # The included `nuget.config` file will allow you to install a nightly
15 | # version of coverlet from the coverlet nightly nuget feed.
16 | #
17 | # dotnet tool install --global coverlet.console --prerelease.
18 | #
19 | # You can build coverlet yourself, but you will need to edit the path to
20 | # coverlet below to point to your local build of the coverlet dll.
21 | #
22 | # If you need help with coverage, feel free to join the Chickensoft Discord.
23 | # https://chickensoft.games
24 |
25 | dotnet build --no-restore
26 |
27 | coverlet \
28 | "./.godot/mono/temp/bin/Debug" --verbosity detailed \
29 | --target $GODOT4 \
30 | --targetargs "--run-tests --coverage --quit-on-finish" \
31 | --format "opencover" \
32 | --output "./coverage/coverage.xml" \
33 | --exclude-by-file "**/test/**/*.cs" \
34 | --exclude-by-file "**/*Microsoft.NET.Test.Sdk.Program.cs" \
35 | --exclude-by-file "**/Godot.SourceGenerators/**/*.cs" \
36 | --exclude-assemblies-without-sources "missingall"
37 |
38 | # Projects included via will be collected in code coverage.
39 | # If you want to exclude them, replace the string below with the names of
40 | # the assemblies to ignore. e.g.,
41 | # ASSEMBLIES_TO_REMOVE="-AssemblyToRemove1;-AssemblyToRemove2"
42 | ASSEMBLIES_TO_REMOVE="-Chickensoft.GoDotNet.Tests"
43 |
44 | reportgenerator \
45 | -reports:"./coverage/coverage.xml" \
46 | -targetdir:"./coverage/report" \
47 | "-assemblyfilters:$ASSEMBLIES_TO_REMOVE" \
48 | "-classfilters:-GodotPlugins.Game.Main" \
49 | -reporttypes:"Html;Badges"
50 |
51 | # Copy badges into their own folder. The badges folder should be included in
52 | # source control so that the README.md in the root can reference the badges.
53 |
54 | mkdir -p ./badges
55 | mv ./coverage/report/badge_branchcoverage.svg ./badges/branch_coverage.svg
56 | mv ./coverage/report/badge_linecoverage.svg ./badges/line_coverage.svg
57 |
58 | # Determine OS, open coverage accordingly.
59 |
60 | case "$(uname -s)" in
61 |
62 | Darwin)
63 | echo 'Mac OS X'
64 | open coverage/report/index.htm
65 | ;;
66 |
67 | Linux)
68 | echo 'Linux'
69 | ;;
70 |
71 | CYGWIN*|MINGW32*|MSYS*|MINGW*)
72 | echo 'MS Windows'
73 | start coverage/report/index.htm
74 | ;;
75 |
76 | *)
77 | echo 'Other OS'
78 | ;;
79 | esac
80 |
--------------------------------------------------------------------------------
/Chickensoft.GoDotNet.Tests/coverage/.gdignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chickensoft-games/GoDotNet/29e0f78466c8295b028307605ba91c2ec01a7eec/Chickensoft.GoDotNet.Tests/coverage/.gdignore
--------------------------------------------------------------------------------
/Chickensoft.GoDotNet.Tests/icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Chickensoft.GoDotNet.Tests/icon.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://da2tcc2mhkfgi"
6 | path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://icon.svg"
14 | dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/lossy_quality=0.7
20 | compress/hdr_compression=1
21 | compress/bptc_ldr=0
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 | svg/scale=1.0
36 | editor/scale_with_editor_scale=false
37 | editor/convert_colors_with_editor_theme=false
38 |
--------------------------------------------------------------------------------
/Chickensoft.GoDotNet.Tests/nuget.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/Chickensoft.GoDotNet.Tests/project.godot:
--------------------------------------------------------------------------------
1 | ; Engine configuration file.
2 | ; It's best edited using the editor UI and not directly,
3 | ; since the parameters that go here are not all obvious.
4 | ;
5 | ; Format:
6 | ; [section] ; section goes between []
7 | ; param=value ; assign values to parameters
8 |
9 | config_version=5
10 |
11 | [application]
12 |
13 | config/name="Chickensoft.GoDotNet.Tests"
14 | run/main_scene="res://test/Tests.tscn"
15 | config/features=PackedStringArray("4.0", "C#", "Mobile")
16 | config/icon="res://icon.svg"
17 |
18 | [dotnet]
19 |
20 | project/assembly_name="Chickensoft.GoDotNet.Tests"
21 |
22 | [rendering]
23 |
24 | renderer/rendering_method="mobile"
25 |
--------------------------------------------------------------------------------
/Chickensoft.GoDotNet.Tests/test/Tests.cs:
--------------------------------------------------------------------------------
1 | namespace Chickensoft.GoDotNet.Tests;
2 |
3 | using System.Reflection;
4 | using Godot;
5 | using GoDotTest;
6 |
7 | public partial class Tests : Node2D {
8 | ///
9 | /// Called when the node enters the scene tree for the first time.
10 | ///
11 | public override void _Ready()
12 | => GoTest.RunTests(Assembly.GetExecutingAssembly(), this);
13 | }
14 |
--------------------------------------------------------------------------------
/Chickensoft.GoDotNet.Tests/test/Tests.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=2 format=3 uid="uid://bv5dxd8hrc5g4"]
2 |
3 | [ext_resource type="Script" path="res://test/Tests.cs" id="1_310o6"]
4 |
5 | [node name="Node2D" type="Node2D"]
6 | script = ExtResource("1_310o6")
7 |
--------------------------------------------------------------------------------
/Chickensoft.GoDotNet.Tests/test/src/AutoloadCacheTest.cs:
--------------------------------------------------------------------------------
1 | namespace Chickensoft.GoDotNet.Tests;
2 |
3 | using Godot;
4 | using GoDotNet;
5 | using GoDotTest;
6 | using Shouldly;
7 |
8 | public class AutoloadCacheTest : TestClass {
9 | private class TestClass { }
10 |
11 | public AutoloadCacheTest(Node testScene) : base(testScene) { }
12 |
13 | [Test]
14 | public void SavesAndLoadsAutoloadFromCache() {
15 | var autoload = new TestClass();
16 | AutoloadCache.Write(typeof(TestClass), autoload);
17 | AutoloadCache.Has(typeof(TestClass)).ShouldBeTrue();
18 | AutoloadCache.Read(typeof(TestClass)).ShouldBeSameAs(autoload);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Chickensoft.GoDotNet.Tests/test/src/extensions/NodeExtensionsTest.cs:
--------------------------------------------------------------------------------
1 | namespace Chickensoft.GoDotNet.Tests;
2 |
3 | using System;
4 | using System.Threading.Tasks;
5 | using Godot;
6 | using GoDotNet;
7 | using GoDotTest;
8 | using Shouldly;
9 |
10 | public partial class TestAutoloadNode : Node { }
11 | public class NodeExtensionsTest : TestClass {
12 | public NodeExtensionsTest(Node testScene) : base(testScene) { }
13 |
14 | [Test]
15 | public async Task TryAutoloadFindsNothing() {
16 | // Must wait until the next frame so that we are running inside "_Process"
17 | // This ensures that the root node children are initialized.
18 | await TestScene.ToSignal(TestScene.GetTree(), "process_frame");
19 | var foundAutoload = TestScene.TryAutoload();
20 | foundAutoload.ShouldBeNull();
21 | }
22 |
23 | [Test]
24 | public async Task AutoloadThrowsOnNotFound() {
25 | await TestScene.ToSignal(TestScene.GetTree(), "process_frame");
26 | Should.Throw(
27 | () => TestScene.Autoload()
28 | );
29 | }
30 |
31 | [Test]
32 | public async Task TryAutoloadFindsAutoloadFromRootAndCache() {
33 | await TestScene.ToSignal(TestScene.GetTree(), "process_frame");
34 | var autoload = new TestAutoloadNode();
35 | var root = TestScene.GetNode("/root/");
36 | root.AddChild(autoload);
37 | var foundAutoload = TestScene.Autoload();
38 | foundAutoload.ShouldNotBeNull();
39 | foundAutoload.ShouldBeSameAs(autoload);
40 | var cachedAutoload = TestScene.Autoload();
41 | cachedAutoload.ShouldNotBeNull();
42 | cachedAutoload.ShouldBeSameAs(autoload);
43 | root.RemoveChild(autoload); // as of Godot 3.5, this doesn't seem to take
44 | // place right away. :( That's why this test runs after the two above.
45 | autoload.QueueFree();
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Chickensoft.GoDotNet.Tests/test/src/extensions/StringExtensionsTest.cs:
--------------------------------------------------------------------------------
1 | namespace Chickensoft.GoDotNet.Tests;
2 |
3 | using Godot;
4 | using GoDotNet;
5 | using GoDotTest;
6 | using Shouldly;
7 |
8 | public class StringExtensionsTest : TestClass {
9 | public StringExtensionsTest(Node testScene) : base(testScene) { }
10 |
11 | [Test]
12 | public void ToNameCaseWorksOnNull() {
13 | string str = null!;
14 | str.ToNameCase().ShouldBe("");
15 | }
16 |
17 | [Test]
18 | public void ToNameCaseWorksOnBlank() {
19 | var str = "";
20 | str.ToNameCase().ShouldBe("");
21 | }
22 |
23 | [Test]
24 | public void ToNameCaseWorksOnValue() {
25 | "hello".ToNameCase().ShouldBe("Hello");
26 | " ".ToNameCase().ShouldBe(" ");
27 | "^".ToNameCase().ShouldBe("^");
28 | "1".ToNameCase().ShouldBe("1");
29 | "a".ToNameCase().ShouldBe("A");
30 | "A B C".ToNameCase().ShouldBe("A B C");
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Chickensoft.GoDotNet.Tests/test/src/nodes/SchedulerTest.cs:
--------------------------------------------------------------------------------
1 | namespace Chickensoft.GoDotNet.Tests;
2 |
3 | using System;
4 | using System.Diagnostics;
5 | using Godot;
6 | using GoDotLog;
7 | using GoDotNet;
8 | using GoDotTest;
9 | using LightMock;
10 | using LightMock.Generator;
11 | using LightMoq;
12 | using Shouldly;
13 |
14 | public class SchedulerTest : TestClass {
15 | public SchedulerTest(Node testScene) : base(testScene) { }
16 |
17 | [Test]
18 | public void InitializesFromDefaultConstructor() {
19 | var scheduler = new Scheduler();
20 | scheduler.ShouldBeOfType(typeof(Scheduler));
21 | }
22 |
23 | [Test]
24 | public void InitializesWithCustomConstructor() {
25 | var log = new Mock();
26 | var scheduler = new Scheduler(log.Object);
27 | scheduler.Log.ShouldBeSameAs(log.Object);
28 | }
29 |
30 | [Test]
31 | public void InitializesWithCustomConstructorAndDebugging() {
32 | var log = new Mock();
33 | var isDebugging = true;
34 | var scheduler = new Scheduler(log.Object, isDebugging);
35 | scheduler.Log.ShouldBeSameAs(log.Object);
36 | scheduler._Ready();
37 | scheduler.IsDebugging.ShouldBe(isDebugging);
38 | }
39 |
40 | [Test]
41 | public void RunsScheduledCallback() {
42 | var scheduler = new Scheduler();
43 | var called = false;
44 | scheduler.NextFrame(() => called = true);
45 | scheduler._Process(0);
46 | called.ShouldBeTrue();
47 | }
48 |
49 | [Test]
50 | public void RunsScheduledCallbackAndHandlesErrorInDebug() {
51 | var log = new Mock();
52 | var scheduler = new Scheduler(log.Object, true);
53 | log.Setup(l => l.Run(The.IsAnyValue, The>.IsAnyValue))
54 | .Callback>(
55 | (action, errorHandler) => {
56 | try {
57 | action();
58 | }
59 | catch (Exception e) {
60 | errorHandler(e);
61 | }
62 | }
63 | );
64 |
65 | log.Setup(l => l.Print(
66 | "A callback scheduled in a previous frame threw " +
67 | "an error with the following stack trace."
68 | ));
69 | log.Setup(l => l.Print(The.IsAnyValue));
70 | scheduler.NextFrame(() => throw new InvalidOperationException());
71 | scheduler._Process(0);
72 | log.VerifyAll();
73 | scheduler._Process(0);
74 | }
75 |
76 | [Test]
77 | public void RunsScheduledCallbackAndHandlesErrorInProduction() {
78 | var log = new Mock();
79 | var scheduler = new Scheduler(log.Object, false);
80 | log.Setup(l => l.Run(
81 | The.IsAnyValue, The>.IsAnyValue
82 | )).Callback>(
83 | (action, errorHandler) => {
84 | try {
85 | action();
86 | }
87 | catch (Exception e) {
88 | errorHandler(e);
89 | }
90 | }
91 | );
92 | scheduler.NextFrame(() => throw new InvalidOperationException());
93 | scheduler._Process(0);
94 | log.VerifyAll();
95 | scheduler._Process(0);
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/Chickensoft.GoDotNet.Tests/test/src/state/MachineTest.cs:
--------------------------------------------------------------------------------
1 | namespace Chickensoft.GoDotNet.Tests;
2 |
3 | using Godot;
4 | using GoDotNet;
5 | using GoDotTest;
6 | using Shouldly;
7 |
8 | public class MachineTest : TestClass {
9 | private interface ITestState : IMachineState { }
10 | private record TestStateA : ITestState {
11 | public bool CanTransitionTo(ITestState state)
12 | => state is TestStateB;
13 | }
14 | private record TestStateB : ITestState {
15 | public bool CanTransitionTo(ITestState state)
16 | => state is TestStateC;
17 | }
18 | private record TestStateC : ITestState {
19 | public bool CanTransitionTo(ITestState state)
20 | => state is TestStateA;
21 | }
22 |
23 | private record TestStateD : ITestState { }
24 |
25 | public MachineTest(Node testScene) : base(testScene) { }
26 |
27 | [Test]
28 | public void Instantiates() {
29 | var machine = new Machine(new TestStateA());
30 | machine.State.ShouldBe(new TestStateA());
31 | }
32 |
33 | [Test]
34 | public void InstantiatesWithListener() {
35 | var called = false;
36 | void onChanged(ITestState state) {
37 | state.ShouldBe(new TestStateA());
38 | called = true;
39 | }
40 | var machine = new Machine(new TestStateA(), onChanged);
41 | called.ShouldBeTrue();
42 | machine.State.ShouldBe(new TestStateA());
43 | }
44 |
45 | [Test]
46 | public void UpdatesStateAndAnnounces() {
47 | var machine = new Machine(new TestStateA());
48 |
49 | var called = false;
50 | void onChanged(ITestState state) {
51 | state.ShouldBe(new TestStateB());
52 | called = true;
53 | }
54 |
55 | machine.OnChanged += onChanged;
56 | machine.Update(new TestStateB());
57 | machine.State.ShouldBe(new TestStateB());
58 | called.ShouldBeTrue();
59 | }
60 |
61 | [Test]
62 | public void DoesNothingOnSameState() {
63 | var machine = new Machine(new TestStateA());
64 | var called = false;
65 | void onChanged(ITestState state) => called = true;
66 | machine.OnChanged += onChanged;
67 | machine.Update(new TestStateA());
68 | called.ShouldBeFalse();
69 | }
70 |
71 | [Test]
72 | public void ThrowsWhenTransitioningToInvalidNextState() {
73 | var machine = new Machine(new TestStateA());
74 | machine.State.CanTransitionTo(new TestStateC()).ShouldBeFalse();
75 | Should.Throw>(
76 | () => machine.Update(new TestStateC())
77 | );
78 | }
79 |
80 | [Test]
81 | public void DefaultStateTransitionsToAnything() {
82 | var machine = new Machine(new TestStateD());
83 | machine.State.CanTransitionTo(new TestStateA()).ShouldBeTrue();
84 | machine.State.CanTransitionTo(new TestStateB()).ShouldBeTrue();
85 | machine.State.CanTransitionTo(new TestStateC()).ShouldBeTrue();
86 | }
87 |
88 | [Test]
89 | public void UpdatesStateFromAnnouncementPreservesOrdering() {
90 | var machine = new Machine(new TestStateA());
91 |
92 | void onChanged(ITestState state) {
93 | if (state is TestStateB) {
94 | machine.Update(new TestStateC());
95 | }
96 | }
97 |
98 | machine.OnChanged += onChanged;
99 | machine.Update(new TestStateB());
100 | machine.State.ShouldBe(new TestStateC());
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/Chickensoft.GoDotNet.Tests/test/src/state/NotifierTest.cs:
--------------------------------------------------------------------------------
1 | namespace Chickensoft.GoDotNet.Tests;
2 |
3 | using Godot;
4 | using GoDotNet;
5 | using GoDotTest;
6 | using Shouldly;
7 |
8 | public class NotifierTest : TestClass {
9 | public NotifierTest(Node testScene) : base(testScene) { }
10 |
11 | [Test]
12 | public void Instantiates() {
13 | var notifier = new Notifier("a");
14 | notifier.Value.ShouldBe("a");
15 | notifier.Previous.ShouldBeNull();
16 | }
17 |
18 | [Test]
19 | public void InstantiatesWithListener() {
20 | var called = false;
21 | void onChanged(string value, string? previous) {
22 | previous.ShouldBeNull();
23 | value.ShouldBe("a");
24 | called = true;
25 | }
26 | var notifier = new Notifier("a", onChanged);
27 | called.ShouldBeTrue();
28 | notifier.Value.ShouldBe("a");
29 | notifier.Previous.ShouldBeNull();
30 | }
31 |
32 | [Test]
33 | public void DoesNothingOnSameValue() {
34 | var notifier = new Notifier("a");
35 | var called = false;
36 | void onChanged(string value, string? previous) => called = true;
37 | notifier.OnChanged += onChanged;
38 | notifier.Update("a");
39 | called.ShouldBeFalse();
40 | notifier.Previous.ShouldBeNull();
41 | }
42 |
43 | [Test]
44 | public void UpdatesValue() {
45 | var notifier = new Notifier("a");
46 | var calledOnChanged = false;
47 | var calledOnUpdated = false;
48 | void onChanged(string value, string? previous) {
49 | calledOnChanged = true;
50 | value.ShouldBe("b");
51 | previous.ShouldBe("a");
52 | notifier.Previous.ShouldBe("a");
53 | }
54 | void onUpdated(string value) {
55 | calledOnUpdated = true;
56 | value.ShouldBe("b");
57 | notifier.Previous.ShouldBe("a");
58 | }
59 | notifier.OnChanged += onChanged;
60 | notifier.OnUpdated += onUpdated;
61 | notifier.Update("b");
62 | calledOnChanged.ShouldBeTrue();
63 | calledOnUpdated.ShouldBeTrue();
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/Chickensoft.GoDotNet.Tests/test/src/utils/RngTest.cs:
--------------------------------------------------------------------------------
1 | namespace Chickensoft.GoDotNet.Tests;
2 |
3 | using Godot;
4 | using GoDotNet;
5 | using GoDotTest;
6 | using Shouldly;
7 |
8 | public class NotRandomInt : System.Random {
9 | private readonly int _number;
10 |
11 | public int MinValue { get; private set; }
12 | public int MaxValue { get; private set; }
13 |
14 | public NotRandomInt(int number) : base(0) {
15 | _number = number;
16 | }
17 |
18 | public override int Next(int minValue, int maxValue) {
19 | MinValue = minValue;
20 | MaxValue = maxValue;
21 | return _number;
22 | }
23 |
24 | public override int Next() => _number;
25 | }
26 |
27 | public class NotRandomDouble : System.Random {
28 | private readonly double _number;
29 |
30 | public NotRandomDouble(double number) : base(0) {
31 | _number = number;
32 | }
33 |
34 | public override double NextDouble() => _number;
35 | }
36 |
37 | public class RngTest : TestClass {
38 | public RngTest(Node testScene) : base(testScene) { }
39 |
40 | [Test]
41 | public void DefaultInitializer() {
42 | var rng = new Rng();
43 | rng.Seed.ShouldBeOfType();
44 | rng.Generator.ShouldBeOfType();
45 | }
46 |
47 | [Test]
48 | public void SeedInitializer() {
49 | var seed = 123;
50 | var rng = new Rng(seed);
51 | rng.Seed.ShouldBe(seed);
52 | rng.Generator.ShouldBeOfType();
53 | }
54 |
55 | [Test]
56 | public void ManualInitializer() {
57 | var seed = 123;
58 | var generator = new System.Random(seed);
59 | var rng = new Rng(seed, generator);
60 | rng.Seed.ShouldBe(seed);
61 | rng.Generator.ShouldBe(generator);
62 | }
63 |
64 | [Test]
65 | public void NextInt() {
66 | var rng = new Rng(0, new NotRandomInt(123));
67 | rng.NextInt().ShouldBe(123);
68 | }
69 |
70 | [Test]
71 | public void NextFloat() {
72 | var rng = new Rng(0, new NotRandomDouble(0.123d));
73 | rng.NextFloat().ShouldBe(0.123f);
74 | }
75 |
76 | [Test]
77 | public void NextDouble() {
78 | var rng = new Rng(0, new NotRandomDouble(0.123d));
79 | rng.NextDouble().ShouldBe(0.123d);
80 | }
81 |
82 | [Test]
83 | public void RangeInt() {
84 | var gen = new NotRandomInt(5);
85 | var rng = new Rng(0, gen);
86 | rng.RangeInt(0, 10).ShouldBe(5);
87 | gen.MinValue.ShouldBe(0);
88 | gen.MaxValue.ShouldBe(10);
89 | }
90 |
91 | [Test]
92 | public void RangeFloat() {
93 | var gen = new NotRandomDouble(0.5d);
94 | var rng = new Rng(0, gen);
95 | rng.RangeFloat(1f, 10f).ShouldBe(5.5f);
96 | }
97 |
98 | [Test]
99 | public void RangeDouble() {
100 | var gen = new NotRandomDouble(0.5d);
101 | var rng = new Rng(0, gen);
102 | rng.RangeDouble(1d, 10d).ShouldBe(5.5d);
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/Chickensoft.GoDotNet.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.0.31903.59
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Chickensoft.GoDotNet", "Chickensoft.GoDotNet\Chickensoft.GoDotNet.csproj", "{FD2D0482-115D-4B55-A95A-5DF468BF19D5}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Chickensoft.GoDotNet.Tests", "Chickensoft.GoDotNet.Tests\Chickensoft.GoDotNet.Tests.csproj", "{73F2185F-1EB8-4817-BCCD-A3887844109D}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(SolutionProperties) = preSolution
16 | HideSolutionNode = FALSE
17 | EndGlobalSection
18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
19 | {FD2D0482-115D-4B55-A95A-5DF468BF19D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
20 | {FD2D0482-115D-4B55-A95A-5DF468BF19D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
21 | {FD2D0482-115D-4B55-A95A-5DF468BF19D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
22 | {FD2D0482-115D-4B55-A95A-5DF468BF19D5}.Release|Any CPU.Build.0 = Release|Any CPU
23 | {73F2185F-1EB8-4817-BCCD-A3887844109D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
24 | {73F2185F-1EB8-4817-BCCD-A3887844109D}.Debug|Any CPU.Build.0 = Debug|Any CPU
25 | {73F2185F-1EB8-4817-BCCD-A3887844109D}.Release|Any CPU.ActiveCfg = Debug|Any CPU
26 | {73F2185F-1EB8-4817-BCCD-A3887844109D}.Release|Any CPU.Build.0 = Debug|Any CPU
27 | EndGlobalSection
28 | EndGlobal
29 |
--------------------------------------------------------------------------------
/Chickensoft.GoDotNet/Chickensoft.GoDotNet.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net6.0
4 | true
5 | preview
6 | true
7 | enable
8 | true
9 | Chickensoft.GoDotNet
10 | true
11 | ./nupkg
12 | portable
13 |
14 | Chickensoft.GoDotNet
15 | 1.5.16
16 | State machines, notifiers, and other utilities for C# Godot development.
17 | © 2023 Chickensoft
18 | Chickensoft
19 | Chickensoft
20 |
21 | Chickensoft.GoDotNet
22 | Chickensoft.GoDotNet release.
23 | icon.png
24 | Godot;State Machine;Deterministic;Finite;FSM;Extensions;Notifier;Listener;Observable;Chickensoft;Gamedev;Utility;Utilities
25 | README.md
26 | LICENSE
27 | https://github.com/chickensoft-games/GoDotNet
28 |
29 | git
30 | https://github.com/chickensoft-games/GoDotNet
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | all
46 | runtime; build; native; contentfiles; analyzers
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/Chickensoft.GoDotNet/icon.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:18ed2854b10643db64f9c5644af05d2dacb5d8e8ae2df2998ef94cad29c3013e
3 | size 30347
4 |
--------------------------------------------------------------------------------
/Chickensoft.GoDotNet/src/AutoloadCache.cs:
--------------------------------------------------------------------------------
1 | namespace Chickensoft.GoDotNet;
2 |
3 | using System;
4 | using System.Collections.Generic;
5 |
6 | ///
7 | /// A static class used to cache autoloads whenever they are fetched. This
8 | /// prevents from having to fetch the
9 | /// root node children on every invocation.
10 | ///
11 | public class AutoloadCache {
12 | private static readonly Dictionary _cache = new();
13 |
14 | ///
15 | /// Save a value to the cache.
16 | ///
17 | /// Type of the node.
18 | /// Node to save.
19 | public static void Write(Type type, object value) => _cache.Add(type, value);
20 | ///
21 | /// Read a value from the cache.
22 | ///
23 | /// Type of the node.
24 | /// Node in the cache.
25 | public static object Read(Type type) => _cache[type];
26 | ///
27 | /// Check if a value is in the cache.
28 | ///
29 | /// Type of the node.
30 | /// True if the value is saved in the cache.
31 | public static bool Has(Type type) => _cache.ContainsKey(type);
32 | }
33 |
--------------------------------------------------------------------------------
/Chickensoft.GoDotNet/src/extensions/NodeExtensions.cs:
--------------------------------------------------------------------------------
1 | namespace Chickensoft.GoDotNet;
2 |
3 | using System;
4 | using Godot;
5 |
6 | ///
7 | /// A collection of extension methods for Godot nodes.
8 | ///
9 | public static class NodeExtensions {
10 | ///
11 | ///
12 | /// Returns an instance of a Godot Autoload singleton. Implemented as an
13 | /// extension of Godot.Node, for your convenience.
14 | ///
15 | ///
16 | /// This can potentially return the root node of the scene if and only if
17 | /// you specify the type of the root node, which should be unlikely (an
18 | /// edge case that can occur since this searches the root node's children).
19 | ///
20 | ///
21 | /// This respects type inheritance. If multiple autoloads extend the same
22 | /// type, this returns the first autoload that is assignable to the
23 | /// specified type. For best results, ensure the autoload type hierarchy
24 | /// does not overlap.
25 | ///
26 | ///
27 | /// The node (receiver) used to get the scene root.
28 | ///
29 | /// The type of autoload to find.
30 | /// The autoload instance, or throws.
31 | /// Thrown when an autoload of the
32 | /// given type cannot be found.
33 | public static T Autoload(this Node node) where T : Node
34 | => TryAutoload(node) ?? throw new InvalidOperationException(
35 | "No singleton found for type " + typeof(T).Name
36 | );
37 |
38 | ///
39 | /// Tries to find an autoload of the specified type. If none is found,
40 | /// returns null.
41 | ///
42 | /// The node (receiver) used to get the scene root.
43 | ///
44 | /// The type of autoload to find.
45 | /// The autoload, if found.
46 | public static T? TryAutoload(this Node node) {
47 | var type = typeof(T);
48 | if (AutoloadCache.Has(type)) { return (T)AutoloadCache.Read(type); }
49 | var root = node.GetNode("/root/");
50 | foreach (var autoload in (Godot.Collections.Array)root.GetChildren()) {
51 | if (autoload is T singleton) {
52 | AutoloadCache.Write(type, singleton);
53 | return singleton;
54 | }
55 | }
56 | return default;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Chickensoft.GoDotNet/src/extensions/StringExtensions.cs:
--------------------------------------------------------------------------------
1 | namespace Chickensoft.GoDotNet;
2 |
3 | ///
4 | /// String extensions.
5 | ///
6 | public static class StringExtensions {
7 | ///
8 | /// Returns the string with the first letter capitalized.
9 | ///
10 | /// String receiver.
11 | /// The string with the first letter capitalized.
12 | public static string ToNameCase(this string input) =>
13 | input switch {
14 | null => "",
15 | "" => "",
16 | _ => input[0].ToString().ToUpper() + input[1..],
17 | };
18 | }
19 |
--------------------------------------------------------------------------------
/Chickensoft.GoDotNet/src/nodes/Scheduler.cs:
--------------------------------------------------------------------------------
1 | namespace Chickensoft.GoDotNet;
2 |
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Diagnostics;
6 | using Godot;
7 | using GoDotLog;
8 |
9 | ///
10 | /// The scheduler helps queue up callbacks and run them at the desired time.
11 | ///
12 | public partial class Scheduler : Node {
13 | public ILog Log { get; }
14 | private readonly Queue _actions = new();
15 | private readonly Dictionary _stackTraces = new();
16 | private readonly Dictionary _callers = new();
17 | private readonly bool? _isDebuggingOverride;
18 | private bool _isDebugBuild;
19 |
20 | /// True if the scheduler is running in debug mode.
21 | public bool IsDebugging => _isDebuggingOverride ?? _isDebugBuild;
22 |
23 | /// Creates a new scheduler.
24 | public Scheduler() {
25 | Log = new GDLog(nameof(Scheduler));
26 | }
27 |
28 | /// Creates a new scheduler.
29 | /// Scheduler log.
30 | /// Debug mode (true or false, or null
31 | /// to infer).
32 | public Scheduler(ILog log, bool? isDebuggingOverride = null) {
33 | Log = log;
34 | _isDebuggingOverride = isDebuggingOverride;
35 | }
36 |
37 | ///
38 | public override void _Ready()
39 | => _isDebugBuild = OS.IsDebugBuild();
40 |
41 | ///
42 | public override void _Process(double delta) {
43 | if (_actions.Count == 0) { return; }
44 | do {
45 | var action = _actions.Dequeue();
46 | Log.Run(
47 | action,
48 | IsDebugging
49 | ? HandleScheduledActionError(action)
50 | : (e) => { }
51 | );
52 | } while (_actions.Count > 0);
53 | }
54 |
55 | private Action HandleScheduledActionError(Action action)
56 | => (Exception e) => {
57 | var stackTrace = _stackTraces[action];
58 | _stackTraces.Remove(action);
59 | Log.Print(
60 | "A callback scheduled in a previous frame threw an error with " +
61 | "the following stack trace."
62 | );
63 | Log.Print(stackTrace);
64 | };
65 |
66 | ///
67 | /// Schedule a callback to run on the next frame.
68 | ///
69 | /// Callback to run.
70 | public void NextFrame(Action action) {
71 | _actions.Enqueue(action);
72 | if (IsDebugging) {
73 | _stackTraces.Add(action, new StackTrace());
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/Chickensoft.GoDotNet/src/state/Machine.cs:
--------------------------------------------------------------------------------
1 | namespace Chickensoft.GoDotNet;
2 |
3 | using System;
4 | using System.Collections.Generic;
5 |
6 | ///
7 | /// Exception thrown when attempting to transition between states
8 | /// that are incompatible.
9 | ///
10 | /// Machine state.
11 | public class InvalidStateTransitionException : Exception {
12 | /// Current state.
13 | public TState Current;
14 | /// Attempted next state which was invalid.
15 | public TState Desired;
16 |
17 | ///
18 | /// Creates a new invalid state transition exception.
19 | ///
20 | /// Current state.
21 | /// Attempted next state which was invalid.
22 | public InvalidStateTransitionException(TState current, TState desired) :
23 | base($"Invalid state transition between ${current} and ${desired}.") {
24 | Current = current;
25 | Desired = desired;
26 | }
27 | }
28 |
29 | ///
30 | /// A record type that all machine states must inherit from.
31 | ///
32 | /// Because records are reference types with value-based equality, states
33 | /// can be compared easily by the state machine.
34 | ///
35 | ///
36 | /// All state types must implement `CanTransitionTo` which returns true
37 | /// if a given state can be transitioned to from the current state.
38 | ///
39 | ///
40 | /// Machine state type.
41 | public interface IMachineState {
42 | ///
43 | /// Determines whether the given state can be transitioned to from the
44 | /// current state.
45 | ///
46 | /// The requested next state.
47 | /// True to allow the state transition, false to prevent.
48 | bool CanTransitionTo(TState state) => true;
49 | }
50 |
51 | ///
52 | /// Read-only interface for a machine. Expose machines as this interface
53 | /// when you want to allow them to be observed and read, but not updated.
54 | ///
55 | ///
56 | public interface IReadOnlyMachine where TState : IMachineState {
57 | ///
58 | /// Event handler for when the machine's state changes.
59 | ///
60 | /// The new state of the machine.
61 | delegate void Changed(TState state);
62 |
63 | /// Event emitted when the machine's state changes.
64 | event Changed? OnChanged;
65 |
66 | /// The current state of the machine.
67 | TState State { get; }
68 | }
69 |
70 | ///
71 | ///
72 | /// A simple implementation of a state machine. Events an emit when the state
73 | /// is changed.
74 | ///
75 | ///
76 | /// Not intended to be subclassed — instead, use instances of this in a
77 | /// compositional pattern.
78 | ///
79 | ///
80 | /// States can implement `CanTransitionTo` to prevent transitions to invalid
81 | /// states.
82 | ///
83 | ///
84 | /// Type of state used by the machine.
85 | public sealed class Machine : IReadOnlyMachine
86 | where TState : IMachineState {
87 | ///
88 | /// Creates a new machine with the given initial state.
89 | ///
90 | /// Initial state of the machine.
91 | ///
92 | public Machine(
93 | TState state,
94 | IReadOnlyMachine.Changed? onChanged = null
95 | ) {
96 | State = state;
97 | if (onChanged != null) { OnChanged += onChanged; }
98 | Announce();
99 | }
100 |
101 | ///
102 | public TState State { get; private set; }
103 |
104 | ///
105 | public event IReadOnlyMachine.Changed? OnChanged;
106 |
107 | ///
108 | /// Whether we're currently in the process of changing the state (or not).
109 | ///
110 | public bool IsBusy { get; private set; }
111 |
112 | private Queue PendingTransitions { get; set; }
113 | = new Queue();
114 |
115 | ///
116 | /// Adds a value to the queue of pending transitions. If the next state
117 | /// is equivalent to the current state, the state will not be changed.
118 | /// If the next state cannot be transitioned to from the current state,
119 | /// the state will not be changed and a warning will be issued before
120 | /// attempting to transition to any subsequent queued states.
121 | ///
122 | /// State for the machine to transition to.
123 | ///
124 | public void Update(TState value) {
125 | // Because machine state can be updated when firing state events from
126 | // previous state updates, we need to make sure we don't allow another
127 | // announce loop to begin while we're already announcing state updates.
128 | //
129 | // Instead, we just make sure we add the transition to the list of
130 | // pending transitions. State machines are guaranteed to enter each state
131 | // requested in the order they are requested (or throw an error if the
132 | // requested sequence is not comprised of valid transitions).
133 | PendingTransitions.Enqueue(value);
134 |
135 | if (IsBusy) {
136 | return;
137 | }
138 |
139 | IsBusy = true;
140 |
141 | while (PendingTransitions.Count > 0) {
142 | var state = PendingTransitions.Dequeue();
143 | if (State.Equals(state)) {
144 | continue;
145 | }
146 |
147 | if (State.CanTransitionTo(state)) {
148 | State = state;
149 | Announce();
150 | }
151 | else {
152 | IsBusy = false;
153 | throw new InvalidStateTransitionException(State, state);
154 | }
155 | }
156 |
157 | IsBusy = false;
158 | }
159 |
160 | ///
161 | /// Announces the current state to any listeners.
162 | /// calls this automatically if the new state is
163 | /// different from the previous state.
164 | ///
165 | /// Call this whenever you want to force a re-announcement.
166 | ///
167 | public void Announce() => OnChanged?.Invoke(State);
168 | }
169 |
--------------------------------------------------------------------------------
/Chickensoft.GoDotNet/src/state/Notifier.cs:
--------------------------------------------------------------------------------
1 | namespace Chickensoft.GoDotNet;
2 | ///
3 | /// Read-only interface for a notifier. Expose notifiers as this interface
4 | /// when you want to allow them to be observed and read, but not updated.
5 | ///
6 | /// Notifier value type.
7 | public interface IReadOnlyNotifier {
8 | ///
9 | /// Signature of the event handler for when the value changes. The current
10 | /// and previous values are provided to allow listeners to react to changes.
11 | ///
12 | /// Current value of the notifier.
13 | /// Previous value of the notifier.
14 | delegate void Changed(TData current, TData? previous);
15 |
16 | ///
17 | /// Signature of the event handler for when the value changes.
18 | ///
19 | /// Current value of the notifier.
20 | delegate void Updated(TData value);
21 |
22 | ///
23 | /// Event emitted when the current value of the notifier has changed.
24 | /// Event listeners will receive both the current and previous values.
25 | ///
26 | event Changed? OnChanged;
27 |
28 | ///
29 | /// Event emitted when the current value of the notifier has changed.
30 | /// Event listeners will receive only the current value. Subscribe to
31 | /// if you need both the current and previous values.
32 | ///
33 | event Updated? OnUpdated;
34 |
35 | /// Current notifier value.
36 | TData Value { get; }
37 | }
38 |
39 | ///
40 | /// An object which stores a value and emits an event whenever the value
41 | /// changes.
42 | ///
43 | /// Type of data emitted.
44 | public sealed class Notifier : IReadOnlyNotifier {
45 | ///
46 | /// Creates a new notifier with the given initial value and optional
47 | /// event handler.
48 | ///
49 | /// Initial value (should not be null).
50 | /// Event handler, if any.
51 | public Notifier(
52 | TData initialValue,
53 | IReadOnlyNotifier.Changed? onChanged = null
54 | ) {
55 | if (onChanged != null) { OnChanged += onChanged; }
56 | Value = initialValue;
57 | Previous = default;
58 | Announce();
59 | }
60 |
61 | ///
62 | /// Previous value of the notifier, if any. This is `default` when the
63 | /// notifier has just been created.
64 | ///
65 | public TData? Previous { get; private set; }
66 |
67 | ///
68 | public TData Value { get; private set; }
69 |
70 | ///
71 | public event IReadOnlyNotifier.Changed? OnChanged;
72 |
73 | ///
74 | public event IReadOnlyNotifier.Updated? OnUpdated;
75 |
76 | ///
77 | /// Updates the notifier value. Any listeners will be called with the
78 | /// new and previous values if the new value is not equal to the old value
79 | /// (as determined by Object.Equals).
80 | ///
81 | /// New value of the notifier.
82 | public void Update(TData value) {
83 | if (Equals(Value, value)) { return; }
84 | Previous = Value;
85 | Value = value;
86 | Announce();
87 | }
88 |
89 | ///
90 | /// Announces the current and previous values to any listeners.
91 | /// calls this automatically if the new value is
92 | /// different from the previous value.
93 | ///
94 | /// Call this whenever you want to force a re-announcement.
95 | ///
96 | public void Announce() {
97 | OnChanged?.Invoke(Value, Previous);
98 | OnUpdated?.Invoke(Value);
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/Chickensoft.GoDotNet/src/utils/Rng.cs:
--------------------------------------------------------------------------------
1 | namespace Chickensoft.GoDotNet;
2 |
3 | ///
4 | /// A random number generator that wraps and
5 | /// provides additional convenience functions for working with floats, doubles,
6 | /// and other types.
7 | ///
8 | public interface IRng {
9 | ///
10 | /// Seed used for the random number generator.
11 | ///
12 | int Seed { get; }
13 | ///
14 | /// Random number generator used to produce random numbers.
15 | ///
16 | System.Random Generator { get; }
17 | ///
18 | /// Returns a non-negative random integer.
19 | ///
20 | /// A 32-bit signed integer that is greater than or equal to 0 and
21 | /// less than int.MaxValue
22 | int NextInt();
23 | ///
24 | /// Returns a random floating-point number that is greater than or equal to
25 | /// 0.0, and less than 1.0.
26 | ///
27 | float NextFloat();
28 | ///
29 | /// Returns a random floating-point number that is greater than or equal to
30 | /// 0.0, and less than 1.0.
31 | ///
32 | double NextDouble();
33 | ///
34 | /// Generates a random integer between min (inclusive) and max (exclusive).
35 | ///
36 | /// Minimum value (inclusive).
37 | /// Maximum value (exclusive).
38 | /// An integer between [min, max)
39 | int RangeInt(int min, int max);
40 | ///
41 | /// Generates a random float between min (inclusive) and max (exclusive).
42 | ///
43 | /// Minimum value (inclusive).
44 | /// Maximum value (exclusive).
45 | /// A float between [min, max)
46 | float RangeFloat(float min, float max);
47 | ///
48 | /// Generates a random double between min (inclusive) and max (exclusive).
49 | ///
50 | /// Minimum value (inclusive).
51 | /// Maximum value (exclusive).
52 | /// A double between [min, max)
53 | double RangeDouble(double min, double max);
54 | }
55 |
56 | ///
57 | /// Random number generator default implementation.
58 | ///
59 | public record Rng : IRng {
60 | ///
61 | public int Seed { get; init; }
62 | ///
63 | public System.Random Generator { get; private set; }
64 |
65 | ///
66 | /// Creates a new random number generator with the current value of
67 | /// as the seed.
68 | ///
69 | public Rng() {
70 | Seed = System.Environment.TickCount;
71 | Generator = new System.Random(Seed);
72 | }
73 |
74 | ///
75 | /// Creates a new random number generator with the given seed.
76 | ///
77 | /// Random number generator seed.
78 | public Rng(int seed) {
79 | Generator = new System.Random(seed);
80 | Seed = seed;
81 | }
82 |
83 | ///
84 | /// Creates a new random number generator with the given seed and generator.
85 | ///
86 | /// Random number generator seed.
87 | /// Random number generator.
88 | public Rng(int seed, System.Random generator) {
89 | Generator = generator;
90 | Seed = seed;
91 | }
92 |
93 | ///
94 | public int NextInt() => Generator.Next();
95 |
96 | ///
97 | public float NextFloat() => (float)Generator.NextDouble();
98 |
99 | ///
100 | public double NextDouble() => Generator.NextDouble();
101 |
102 | ///
103 | public int RangeInt(int min, int max) => Generator.Next(min, max);
104 |
105 | ///
106 | public float RangeFloat(float min, float max)
107 | => (float)(
108 | (Generator.NextDouble() * ((double)max - (double)min)) + (double)min
109 | );
110 |
111 | ///
112 | public double RangeDouble(double min, double max)
113 | => (Generator.NextDouble() * (max - min)) + min;
114 | }
115 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | # MIT License
2 |
3 | Copyright (c) 2023 Chickensoft
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Chickensoft.GoDotNet
2 |
3 | [![Chickensoft Badge][chickensoft-badge]][chickensoft-website] [![Discord][discord-badge]][discord] [![Read the docs][read-the-docs-badge]][docs] ![line coverage][line-coverage] ![branch coverage][branch-coverage]
4 |
5 | > ⛔️🧨💥 **This package is no longer maintained and has been superseded by other projects.**
6 | >
7 | > For better alternatives to the tools provided here, please see the following:
8 | >
9 | > - State machines — use [LogicBlocks].
10 | > - Notifiers - use C#'s [BehaviorSubject] from the [Reactive Extensions].
11 | > - Scheduler - use [Godot's built-in call_deferred mechanism][call-deferred].
12 |
13 | State machines, notifiers, and other utilities for C# Godot development.
14 |
15 | ---
16 |
17 |
18 |
19 |
20 |
21 | > 🚨 Looking for node-based dependency injection with providers and dependents? That functionality has been moved to it's own package, [GoDotDep][go_dot_dep]!
22 |
23 | While developing our own game, we couldn't find any simple C# solutions for simple state machines, notifiers, and mechanisms for avoiding unnecessary marshalling with Godot. So, we built our own systems — hopefully you can benefit from them, too!
24 |
25 | > Are you on discord? If you're building games with Godot and C#, we'd love to see you in the [Chickensoft Discord server][discord]!
26 |
27 | ## Installation
28 |
29 | Find the latest version of [GoDotNet][go_dot_net_nuget] on nuget.
30 |
31 | In your `*.csproj`, add the following snippet in your ``, save, and run `dotnet restore`. Make sure to replace `*VERSION*` with the latest version.
32 |
33 | ```xml
34 |
35 | ```
36 |
37 | GoDotNet is itself written in C# 10 for `netstandard2.1` (the highest language version currently supported by Godot). If you want to setup your project the same way, look no further than the [`GoDotNet.csproj`](GoDotNet.csproj) file for inspiration!
38 |
39 | ## Logging
40 |
41 | Internally, GoDotNet uses [GoDotLog] for logging. GoDotLog allows you to easily create loggers that output nicely formatted, prefixed messages (in addition to asserts and other exception-aware execution utilities).
42 |
43 | ## Autoloads
44 |
45 | An autoload can be fetched easily from any node. Once an autoload is found on the root child, GoDotNet caches it's type, allowing it to be looked up instantaneously without calling into Godot.
46 |
47 | ```csharp
48 | public class MyEntity : Node {
49 | private MyAutoloadType _myAutoload => this.Autoload();
50 |
51 | public override void _Ready() {
52 | _myAutoload.DoSomething();
53 | var otherAutoload = this.Autoload();
54 | }
55 | }
56 | ```
57 |
58 | ## Scheduling
59 |
60 | A `Scheduler` node is included which allows callbacks to be run on the next frame, similar to [CallDeferred][call-deferred]. Unlike `CallDeferred`, the scheduler uses vanilla C# to avoid marshalling types to Godot. Since Godot cannot marshal objects that don't extend `Godot.Object`/`Godot.Reference`, this utility is provided to perform the same function for records, custom types, and C# collections which otherwise couldn't be marshaled between C# and Godot.
61 |
62 | Create a new autoload which extends the scheduler:
63 |
64 | ```csharp
65 | using GoDotNet;
66 |
67 | public class GameScheduler : Scheduler { }
68 | ```
69 |
70 | Add it to your `project.godot` file (preferably the first entry):
71 |
72 | ```ini
73 | [autoload]
74 |
75 | GameScheduler="*res://autoload_folder/GameScheduler.cs"
76 | ```
77 |
78 | ...and simply schedule a callback to run on the next frame:
79 |
80 | ```csharp
81 | this.Autoload().NextFrame(
82 | () => _log.Print("I won't execute until the next frame.")
83 | )
84 | ```
85 |
86 | ## State Machines
87 |
88 | GoDotNet provides a simple state machine implementation that emits a C# event when the state changes (since [Godot signals are more fragile](#signals-and-events)). If you try to update the machine to a state that isn't a valid transition from the current state, it throws an exception. The machine requires an initial state to avoid nullability issues during construction.
89 |
90 | State machines are not extensible — instead, GoDotNet almost always prefers the pattern of [composition over inheritance][composition-inheritance]. The state machine relies on state equality to determine if the state has changed to avoid issuing unnecessary events. Using `record` or other value types for the state makes equality checking work automatically for free.
91 |
92 | States used with a state machine must implement `IMachineState`, where T is just the type of the machine state. Your machine states can optionally implement the method `CanTransitionTo(IMachineState state)`, which should return true if the given "next state" is a valid transition. Otherwise, the default implementation returns `true` to allow transitions to any state.
93 |
94 | To create states for use with a machine, create an interface which implements `IMachineState`. Then, create record types for each state which implement your interface, optionally overriding `CanTransitionTo` for any states which only allow transitions to specific states.
95 |
96 | ```csharp
97 | public interface IGameState : IMachineState { }
98 |
99 | public record GameMainMenuState : IGameState {
100 | public bool CanTransitionTo(IGameState state) => state is GameLoadingState;
101 | }
102 |
103 | public record GameLoadingState : IGameState {
104 | public bool CanTransitionTo(IGameState state) => state is GamePlayingState;
105 | }
106 |
107 | // States can store values!
108 | public record GamePlayingState(string PlayerName) {
109 | public bool CanTransitionTo(IGameState state) => state is GameMainMenuState;
110 | }
111 | ```
112 |
113 | Simply omit implementing `CanTransitionTo` for any states which should allow transitions to any other state.
114 |
115 | ```csharp
116 | public interface GameSuspended : IGameState { }
117 | ```
118 |
119 | Machines are fairly simple to use: create one with an initial state (and optionally register a machine state change event handler). A state machine will announce the state has changed as soon as it is constructed.
120 |
121 | ```csharp
122 | public class GameManager : Node {
123 | private readonly Machine _machine;
124 |
125 | public override void _Ready() {
126 | _machine = new Machine(new GameMainMenuState(), OnChanged);
127 | }
128 |
129 | /// Starts the game.
130 | public void Start(string name) {
131 | _machine.Update(new GameLoadingState());
132 | // do your loading...
133 | // ...
134 | // start the game!
135 | _machine.Update(new GamePlayingState(name);
136 | // Read the current state at any time:
137 | var state = _machine.State;
138 | if (state is GamePlayingState) { /* ... */ }
139 | }
140 |
141 | /// Goes back to the menu.
142 | public void GoToMenu() => _machine.Update(new GameMainMenuState());
143 |
144 | public void OnChanged(IGameState state) {
145 | if (state is GamePlayingState playingState) {
146 | var playerName = playingState.Name();
147 | // ...
148 | }
149 | }
150 | }
151 | ```
152 |
153 | ### Read-only State Machines
154 |
155 | If you want another object to only be able to read the current state of a state machine and subscribe to changes, but not be able to update the state of the machine, you can expose the machine as an `IReadOnlyMachine` instead of as a `Machine`.
156 |
157 | ```csharp
158 | public class AnObjectThatOnlyListensToAMachine {
159 | public IReadOnlyMachine Machine { get; set; }
160 |
161 | // This object can listen and respond to state changes in Machine, but
162 | // cannot mutate the state of the machine via `Machine.Update` :)
163 | }
164 | ```
165 |
166 | ## Notifiers
167 |
168 | A notifier is an object which emits a signal when its value changes. Notifiers are similar to state machines, but they don't care about transitions. Any update that changes the value (determined by comparing the new value with the previous value using `Object.Equals`) will emit a signal. Like state machines, the value of a notifier can never be `null` — make sure you initialize with a valid value!
169 |
170 | Using "value" types (primitive types, records, and structs) with a notifier is a natural fit since notifiers check equality to determine if the value has changed. Like state machines, notifiers also invoke an event to announce their value as soon as they are constructed.
171 |
172 | ```csharp
173 | var notifier = new Notifier("Player", OnPlayerNameChanged);
174 | notifier.Update("Godot");
175 |
176 | // Elsewhere...
177 |
178 | private void OnPlayerNameChanged(string name, string previous) {
179 | _log.Print($"Player name changed from ${previous} to ${name}");
180 | }
181 | ```
182 |
183 | > To easily inject dependencies to descendent nodes, check out [go_dot_dep].
184 |
185 | ### Read-only Notifiers
186 |
187 | As with state machines, a notifiers can be referenced as an `IReadOnlyNotifier` to prevent objects using them from causing unwanted changes. The object(s) that own the notifier can reference it as a `Notifier` and mutate it accordingly, while the listener objects can simply reference it as an `IReadOnlyNotifier`.
188 |
189 | ```csharp
190 | public class AnObjectThatOnlyListensToANotifier {
191 | public IReadOnlyNotifier Notifier { get; set; }
192 |
193 | // This object can listen and respond to changes in the Notifier's value, but
194 | // cannot mutate the value of the notifier via `Notifier.Update` :)
195 | }
196 | ```
197 |
198 | ## Signals and Events
199 |
200 | Godot supports emitting [signals] from C#. Because Godot signals pass through the Godot engine, any arguments given to the signal must be marshalled through Godot, forcing them to be classes which extend `Godot.Object`/`Godot.Reference` (records aren't allowed). Likewise, all the fields in the class must also be the same kind of types so they can be marshalled, and so on.
201 |
202 | It's not possible to have static typing with signal parameters, so you don't find out until runtime if you accidentally passed the wrong parameter. The closest you can do is the following, which wouldn't break at compile time if the receiving function signature happened to be wrong.
203 |
204 | ```csharp
205 | public class ObjectType {
206 | [Signal]
207 | public delegate void DoSomething(string value);
208 | }
209 |
210 | public class MyNode : Node {
211 | // Inside your node
212 | public override void _Ready() {
213 | _ = object.Connect(
214 | nameof(ObjectType.DoSomething),
215 | this,
216 | nameof(MyDoSomething)
217 | );
218 | }
219 |
220 | private void DoSomething(int value) {
221 | // WARNING: Value should be string, not int!!
222 | }
223 | }
224 | ```
225 |
226 | Because of these limitations, GoDotNet will avoid Godot signals except when necessary to interact with Godot components. For communication between C# game logic, it will typically be preferable to use C# events instead.
227 |
228 | ```csharp
229 | // Declare an event signature — no [Signal] attribute necessary.
230 | public delegate void Changed(Type1 value1, Type2 value2);
231 |
232 | // Add an event field to your emitter
233 | public event Changed? OnChanged;
234 |
235 | // Trigger an event from your emitter:
236 | OnChanged?.Invoke(argument1, argument2);
237 |
238 | // Listen to an event in your receiver:
239 | var emitter = new MyEmitterObject();
240 | emitter.OnChanged += MyOnChangedHandler;
241 |
242 | // Event handler in your receiver:
243 | private void MyOnChangedHandler(Type1 value1, Type2 value2) {
244 | // respond to event we received
245 | }
246 | ```
247 |
248 | ## Random Number Generator
249 |
250 | GoDotNet includes a testable convenience wrapper for `System.Random`.
251 |
252 | ```csharp
253 | public partial class MyNode : Node {
254 | public IRng Random { get; set; } = new Rng();
255 |
256 | public override void _Ready() {
257 | int i = Rng.NextInt(); // 0 (inclusive) to int.MaxValue (exclusive)
258 | float f = Rng.NextFloat(); // 0 (inclusive) to 1 (exclusive)
259 | double d = Rng.NextDouble(); // 0 (inclusive) to 1 (exclusive)
260 |
261 | // 0 (inclusive) to 25 (exclusive)
262 | int ir = Rng.RangeInt(0, 25);
263 |
264 | // 0.0f (inclusive) to 25.0f (exclusive)
265 | float fr = Rng.RangeFloat(0f, 25f);
266 |
267 | // 0.0d (inclusive) to 25.0d (exclusive)
268 | double dr = Rng.RangeDouble(0d, 25d);
269 | }
270 | }
271 | ```
272 |
273 | ---
274 |
275 | 🐣 Package generated from a 🐤 Chickensoft Template —
276 |
277 |
278 |
279 |
280 | [chickensoft-badge]: https://raw.githubusercontent.com/chickensoft-games/chickensoft_site/main/static/img/badges/chickensoft_badge.svg
281 | [chickensoft-website]: https://chickensoft.games
282 | [discord-badge]: https://raw.githubusercontent.com/chickensoft-games/chickensoft_site/main/static/img/badges/discord_badge.svg
283 | [discord]: https://discord.gg/gSjaPgMmYW
284 | [read-the-docs-badge]: https://raw.githubusercontent.com/chickensoft-games/chickensoft_site/main/static/img/badges/read_the_docs_badge.svg
285 | [docs]: https://chickensoft.games/docsickensoft%20Discord-%237289DA.svg?style=flat&logo=discord&logoColor=white
286 | [line-coverage]: Chickensoft.GoDotNet.Tests/badges/line_coverage.svg
287 | [branch-coverage]: Chickensoft.GoDotNet.Tests/badges/branch_coverage.svg
288 |
289 |
290 | [go_dot_dep]: https://github.com/chickensoft-games/go_dot_dep
291 | [go_dot_net_nuget]: https://www.nuget.org/packages/Chickensoft.GoDotNet/
292 | [GoDotLog]: https://github.com/chickensoft-games/go_dot_log
293 | [call-deferred]: https://docs.godotengine.org/en/stable/classes/class_object.html#class-object-method-call-deferred
294 | [signals]: https://docs.godotengine.org/en/stable/tutorials/scripting/c_sharp/c_sharp_features.html#c-signals
295 | [composition-inheritance]: https://en.wikipedia.org/wiki/Composition_over_inheritance
296 | [LogicBlocks]: https://github.com/chickensoft-games/LogicBlocks
297 | [BehaviorSubject]: https://learn.microsoft.com/en-us/previous-versions/dotnet/reactive-extensions/hh211949
298 | [Reactive Extensions]: https://www.nuget.org/packages/System.Reactive/
299 |
--------------------------------------------------------------------------------
/cspell.json:
--------------------------------------------------------------------------------
1 | {
2 | "files": [
3 | "**/*.*"
4 | ],
5 | "ignorePaths": [
6 | "**/*.tscn",
7 | "**/*.import",
8 | "Chickensoft.GoDotNet.Tests/badges/**/*.*",
9 | "Chickensoft.GoDotNet.Tests/coverage/**/*.*",
10 | "Chickensoft.GoDotNet.Tests/.godot/**/*.*",
11 | "**/obj/**/*.*",
12 | "**/bin/**/*.*",
13 | "Chickensoft.GoDotNet/nupkg/**/*.*"
14 | ],
15 | "words": [
16 | "assemblyfilters",
17 | "Autoload",
18 | "autoloads",
19 | "automerge",
20 | "branchcoverage",
21 | "brandedoutcast",
22 | "camelcase",
23 | "chickenpackage",
24 | "Chickensoft",
25 | "classfilters",
26 | "contentfiles",
27 | "CYGWIN",
28 | "endregion",
29 | "Gamedev",
30 | "globaltool",
31 | "godotengine",
32 | "godotpackage",
33 | "inheritdoc",
34 | "issuecomment",
35 | "lcov",
36 | "linecoverage",
37 | "methodcoverage",
38 | "missingall",
39 | "msbuild",
40 | "MSYS",
41 | "nameof",
42 | "netstandard",
43 | "NOLOGO",
44 | "nupkg",
45 | "Omnisharp",
46 | "opencover",
47 | "OPTOUT",
48 | "paramref",
49 | "pascalcase",
50 | "renovatebot",
51 | "reportgenerator",
52 | "reporttypes",
53 | "Shouldly",
54 | "structs",
55 | "subfolders",
56 | "targetargs",
57 | "targetdir",
58 | "tscn",
59 | "typeof",
60 | "typeparam",
61 | "typeparamref",
62 | "ulong",
63 | "Xunit"
64 | ]
65 | }
66 |
--------------------------------------------------------------------------------
/docs/renovatebot_pr.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:b63d75d932457b93f79f54e5c4192d201a29aefb4e437a3732c63099ec8f78a1
3 | size 148110
4 |
--------------------------------------------------------------------------------
/docs/spelling_fix.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:870bad263d4bbf803f2278af6e4346cf1e9c07c48c51d85c40ad9b5b63f761b2
3 | size 71136
4 |
--------------------------------------------------------------------------------
/global.json:
--------------------------------------------------------------------------------
1 | {
2 | "sdk": {
3 | "version": "8.0.100-preview.7.23376.3",
4 | "rollForward": "latestMinor"
5 | },
6 | "msbuild-sdks": {
7 | "Godot.NET.Sdk": "4.1.1"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "extends": [
4 | "config:base",
5 | ":semanticCommits"
6 | ],
7 | "prHourlyLimit": 2,
8 | "versioning": "loose",
9 | "packageRules": [
10 | {
11 | "matchPackagePatterns": [
12 | "*"
13 | ],
14 | "groupName": "all dependencies",
15 | "groupSlug": "all-deps",
16 | "automerge": true
17 | },
18 | {
19 | "matchPackagePrefixes": [
20 | "GodotSharp",
21 | "Godot.NET.Sdk"
22 | ],
23 | "allowedVersions": "/^(\\d+\\.\\d+\\.\\d+)(-(beta|rc)\\.(\\d+)(\\.\\d+)*)?$/"
24 | },
25 | {
26 | "matchPackagePrefixes": [
27 | "Chickensoft"
28 | ],
29 | "allowedVersions": "/^(\\d+\\.\\d+\\.\\d+)(-godot(\\d+\\.)+\\d+(-.*)?)?$/"
30 | }
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------