diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..ab1cc559 --- /dev/null +++ b/.clang-format @@ -0,0 +1,185 @@ +# Commented out parameters are those with the same value as base LLVM style. +# We can uncomment them if we want to change their value, or enforce the +# chosen value in case the base style changes (last sync: Clang 14.0). +--- +### General config, applies to all languages ### +BasedOnStyle: LLVM +AccessModifierOffset: -4 +AlignAfterOpenBracket: DontAlign +# AlignArrayOfStructures: None +# AlignConsecutiveMacros: None +# AlignConsecutiveAssignments: None +# AlignConsecutiveBitFields: None +# AlignConsecutiveDeclarations: None +# AlignEscapedNewlines: Right +AlignOperands: DontAlign +AlignTrailingComments: false +# AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: false +# AllowShortEnumsOnASingleLine: true +# AllowShortBlocksOnASingleLine: Never +# AllowShortCaseLabelsOnASingleLine: false +# AllowShortFunctionsOnASingleLine: All +# AllowShortLambdasOnASingleLine: All +# AllowShortIfStatementsOnASingleLine: Never +# AllowShortLoopsOnASingleLine: false +# AlwaysBreakAfterDefinitionReturnType: None +# AlwaysBreakAfterReturnType: None +# AlwaysBreakBeforeMultilineStrings: false +# AlwaysBreakTemplateDeclarations: MultiLine +# AttributeMacros: +# - __capability +# BinPackArguments: true +# BinPackParameters: true +# BraceWrapping: +# AfterCaseLabel: false +# AfterClass: false +# AfterControlStatement: Never +# AfterEnum: false +# AfterFunction: false +# AfterNamespace: false +# AfterObjCDeclaration: false +# AfterStruct: false +# AfterUnion: false +# AfterExternBlock: false +# BeforeCatch: false +# BeforeElse: false +# BeforeLambdaBody: false +# BeforeWhile: false +# IndentBraces: false +# SplitEmptyFunction: true +# SplitEmptyRecord: true +# SplitEmptyNamespace: true +# BreakBeforeBinaryOperators: None +# BreakBeforeConceptDeclarations: true +# BreakBeforeBraces: Attach +# BreakBeforeInheritanceComma: false +# BreakInheritanceList: BeforeColon +# BreakBeforeTernaryOperators: true +# BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: AfterColon +# BreakStringLiterals: true +ColumnLimit: 0 +# CommentPragmas: '^ IWYU pragma:' +# QualifierAlignment: Leave +# CompactNamespaces: false +ConstructorInitializerIndentWidth: 8 +ContinuationIndentWidth: 8 +Cpp11BracedListStyle: false +# DeriveLineEnding: true +# DerivePointerAlignment: false +# DisableFormat: false +# EmptyLineAfterAccessModifier: Never +# EmptyLineBeforeAccessModifier: LogicalBlock +# ExperimentalAutoDetectBinPacking: false +# PackConstructorInitializers: BinPack +ConstructorInitializerAllOnOneLineOrOnePerLine: true +# AllowAllConstructorInitializersOnNextLine: true +# FixNamespaceComments: true +# ForEachMacros: +# - foreach +# - Q_FOREACH +# - BOOST_FOREACH +# IfMacros: +# - KJ_IF_MAYBE +# IncludeBlocks: Preserve +IncludeCategories: + - Regex: '".*"' + Priority: 1 + - Regex: '^<.*\.h>' + Priority: 2 + - Regex: "^<.*" + Priority: 3 +# IncludeIsMainRegex: '(Test)?$' +# IncludeIsMainSourceRegex: '' +# IndentAccessModifiers: false +IndentCaseLabels: true +# IndentCaseBlocks: false +# IndentGotoLabels: true +# IndentPPDirectives: None +# IndentExternBlock: AfterExternBlock +# IndentRequires: false +IndentWidth: 4 +# IndentWrappedFunctionNames: false +# InsertTrailingCommas: None +# JavaScriptQuotes: Leave +# JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +# LambdaBodyIndentation: Signature +# MacroBlockBegin: '' +# MacroBlockEnd: '' +# MaxEmptyLinesToKeep: 1 +# NamespaceIndentation: None +# PenaltyBreakAssignment: 2 +# PenaltyBreakBeforeFirstCallParameter: 19 +# PenaltyBreakComment: 300 +# PenaltyBreakFirstLessLess: 120 +# PenaltyBreakOpenParenthesis: 0 +# PenaltyBreakString: 1000 +# PenaltyBreakTemplateDeclaration: 10 +# PenaltyExcessCharacter: 1000000 +# PenaltyReturnTypeOnItsOwnLine: 60 +# PenaltyIndentedWhitespace: 0 +# PointerAlignment: Right +# PPIndentWidth: -1 +# ReferenceAlignment: Pointer +# ReflowComments: true +# RemoveBracesLLVM: false +# SeparateDefinitionBlocks: Leave +# ShortNamespaceLines: 1 +# SortIncludes: CaseSensitive +# SortJavaStaticImport: Before +# SortUsingDeclarations: true +# SpaceAfterCStyleCast: false +# SpaceAfterLogicalNot: false +# SpaceAfterTemplateKeyword: true +# SpaceBeforeAssignmentOperators: true +# SpaceBeforeCaseColon: false +# SpaceBeforeCpp11BracedList: false +# SpaceBeforeCtorInitializerColon: true +# SpaceBeforeInheritanceColon: true +# SpaceBeforeParens: ControlStatements +# SpaceBeforeParensOptions: +# AfterControlStatements: true +# AfterForeachMacros: true +# AfterFunctionDefinitionName: false +# AfterFunctionDeclarationName: false +# AfterIfMacros: true +# AfterOverloadedOperator: false +# BeforeNonEmptyParentheses: false +# SpaceAroundPointerQualifiers: Default +# SpaceBeforeRangeBasedForLoopColon: true +# SpaceInEmptyBlock: false +# SpaceInEmptyParentheses: false +# SpacesBeforeTrailingComments: 1 +# SpacesInAngles: Never +# SpacesInConditionalStatement: false +# SpacesInContainerLiterals: true +# SpacesInCStyleCastParentheses: false +## Godot TODO: We'll want to use a min of 1, but we need to see how to fix +## our comment capitalization at the same time. +SpacesInLineCommentPrefix: + Minimum: 0 + Maximum: -1 +# SpacesInParentheses: false +# SpacesInSquareBrackets: false +# SpaceBeforeSquareBrackets: false +# BitFieldColonSpacing: Both +# StatementAttributeLikeMacros: +# - Q_EMIT +# StatementMacros: +# - Q_UNUSED +# - QT_REQUIRE_VERSION +TabWidth: 4 +# UseCRLF: false +UseTab: Always +# WhitespaceSensitiveMacros: +# - STRINGIZE +# - PP_STRINGIZE +# - BOOST_PP_STRINGIZE +# - NS_SWIFT_NAME +# - CF_SWIFT_NAME +--- +### C++ specific config ### +Language: Cpp +Standard: c++17 diff --git a/.github/workflows/beehave-ci.yml b/.github/workflows/beehave-ci.yml index 610ae0ea..884ee75f 100644 --- a/.github/workflows/beehave-ci.yml +++ b/.github/workflows/beehave-ci.yml @@ -5,18 +5,18 @@ on: branches: - "godot-4.x" paths-ignore: - - '**.jpg' - - '**.png' - - '**.svg' - - '**.md' - - '**plugin.cfg' + - "**.jpg" + - "**.png" + - "**.svg" + - "**.md" + - "**plugin.cfg" pull_request: paths-ignore: - - '**.jpg' - - '**.png' - - '**.svg' - - '**.md' - - '**plugin.cfg' + - "**.jpg" + - "**.png" + - "**.svg" + - "**.md" + - "**plugin.cfg" workflow_dispatch: concurrency: @@ -31,7 +31,7 @@ jobs: fail-fast: false max-parallel: 10 matrix: - godot-version: ['4.0.4', '4.1.3', '4.2.1'] + godot-version: ["4.2.1"] name: "🤖 CI on Godot ${{ matrix.godot-version }}" uses: ./.github/workflows/unit-tests.yml diff --git a/.gitignore b/.gitignore index b93f1396..63a70445 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,6 @@ reports/ *.dblite *.exp *.lib -*.obj \ No newline at end of file +*.obj + +.vs \ No newline at end of file diff --git a/README.md b/README.md index 06b23db8..8090f150 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ To better understand what branch to choose from for which Godot version, please |---|---|--| |`3.x`|`3.x`|`1.x`| |`4.x`|`4.x`|`2.x`| +|`4.x`|`4.x`|`3.x`| Refer to [this guide](https://bitbra.in/2023/09/03/godot-addon-compatibility.html) for more details behind this structure. @@ -58,12 +59,6 @@ Behavior trees are a modular way to build AI logic for your game. For simple AI, [Learn how to beehave on the official wiki!](https://bitbra.in/beehave/#/manual/) -## Tutorial (Godot 3.5+) - -[bitbrain](https://youtube.com/@bitbraindev) recorded this tutorial to show in more depth how to use this addon: - -[![tutorial-thumbnail](https://img.youtube.com/vi/n0gVEA1dyPQ/0.jpg)](https://www.youtube.com/watch?v=n0gVEA1dyPQ) - # 🥰 Credits - logo designs by [@NathanHoad](https://twitter.com/nathanhoad) & [@StuartDeVille](https://twitter.com/StuartDeVille) diff --git a/SConstruct b/SConstruct index 28260c61..b80d32a8 100644 --- a/SConstruct +++ b/SConstruct @@ -2,8 +2,23 @@ import os import sys +def recursive_glob(rootdir, pattern): + matches = [] + for root, dirnames, filenames in os.walk(rootdir): + for filename in filenames: + if filename.endswith(pattern): + matches.append(os.path.join(root, filename)) + return matches + env = SConscript("godot-cpp/SConstruct") +# Add those directory manually, so we can skip the godot_cpp directory when including headers in C++ files +source_path = [ + os.path.join("godot-cpp", "include","godot_cpp"), + os.path.join("godot-cpp", "gen", "include","godot_cpp") +] +env.Append(CPPPATH=[env.Dir(d) for d in source_path]) + # For the reference: # - CCFLAGS are compilation flags shared between C and C++ # - CFLAGS are for C-specific compilation flags @@ -14,7 +29,8 @@ env = SConscript("godot-cpp/SConstruct") # tweak this if you want to use different folders, or more folders, to store your source code in. env.Append(CPPPATH=["extension/src/"]) -sources = Glob("extension/src/*.cpp") + +sources = recursive_glob('extension/src', '.cpp') if env["platform"] == "macos": library = env.SharedLibrary( diff --git a/addons/beehave/beehave.gdextension b/addons/beehave/beehave.gdextension index 533a6c7f..2f4fe0ad 100644 --- a/addons/beehave/beehave.gdextension +++ b/addons/beehave/beehave.gdextension @@ -1,5 +1,5 @@ [configuration] -entry_symbol = "initialize_beehave_types" +entry_symbol = "beehave_library_init" compatibility_minimum = 4.2 [libraries] @@ -20,4 +20,33 @@ ios.debug = "res://addons/beehave/libs/ios/beehave.ios.template_debug.universal ios.release = "res://addons/beehave/libs/ios/beehave.ios.template_release.universal.dylib" [icons] -BeehaveTree = "res://addons/beehave/icons/tree.svg" \ No newline at end of file +BeehaveTree = "res://addons/beehave/icons/tree.svg" +BeehaveTreeNode = "res://addons/beehave/icons/category_bt.svg" +BeehaveBlackboard = "res://addons/beehave/icons/blackboard.svg" +BeehaveComposite = "res://addons/beehave/icons/category_composite.svg" +BeehaveCompositeRandom = "res://addons/beehave/icons/category_composite.svg" +BeehaveSelector = "res://addons/beehave/icons/selector.svg" +BeehaveSelectorRandom = "res://addons/beehave/icons/selector_random.svg" +BeehaveSelectorReactive = "res://addons/beehave/icons/selector_reactive.svg" +BeehaveSequence = "res://addons/beehave/icons/sequence.svg" +BeehaveSequenceRandom = "res://addons/beehave/icons/sequence_random.svg" +BeehaveSequenceReactive = "res://addons/beehave/icons/sequence_reactive.svg" +BeehaveSequenceStar = "res://addons/beehave/icons/sequence_reactive.svg" +BeehaveSimpleParallel = "res://addons/beehave/icons/simple_parallel.svg" +BeehaveDecorator = "res://addons/beehave/icons/category_decorator.svg" +BeehaveCooldown = "res://addons/beehave/icons/cooldown.svg" +BeehaveDelayer = "res://addons/beehave/icons/delayer.svg" +BeehaveFailer = "res://addons/beehave/icons/failer.svg" +BeehaveSucceeder = "res://addons/beehave/icons/succeeder.svg" +BeehaveInverter = "res://addons/beehave/icons/inverter.svg" +BeehaveLimiter = "res://addons/beehave/icons/limiter.svg" +BeehaveRepeater = "res://addons/beehave/icons/repeater.svg" +BeehaveTimeLimiter = "res://addons/beehave/icons/limiter.svg" +BeehaveUntilFail = "res://addons/beehave/icons/until_fail.svg" +BeehaveLeaf = "res://addons/beehave/icons/category_leaf.svg" +BeehaveAction = "res://addons/beehave/icons/action.svg" +BeehaveCondition = "res://addons/beehave/icons/condition.svg" +BeehaveBlackboardCompare = "res://addons/beehave/icons/condition.svg" +BeehaveBlackboardErase = "res://addons/beehave/icons/action.svg" +BeehaveBlackboardHas = "res://addons/beehave/icons/condition.svg" +BeehaveBlackboardSet = "res://addons/beehave/icons/action.svg" \ No newline at end of file diff --git a/addons/beehave/icons/simple_parallel.svg b/addons/beehave/icons/simple_parallel.svg new file mode 100644 index 00000000..e9c8b008 --- /dev/null +++ b/addons/beehave/icons/simple_parallel.svg @@ -0,0 +1,13 @@ + + simple_parallel + + Layer 1 + + + + + + + + + \ No newline at end of file diff --git a/addons/beehave/icons/until_fail.svg b/addons/beehave/icons/until_fail.svg new file mode 100644 index 00000000..c64a0a06 --- /dev/null +++ b/addons/beehave/icons/until_fail.svg @@ -0,0 +1,45 @@ + + + + + + + + diff --git a/addons/beehave/libs/windows/beehave.windows.editor.x86_64.dll b/addons/beehave/libs/windows/beehave.windows.editor.x86_64.dll index 8795c093..1cfd5b0d 100644 Binary files a/addons/beehave/libs/windows/beehave.windows.editor.x86_64.dll and b/addons/beehave/libs/windows/beehave.windows.editor.x86_64.dll differ diff --git a/addons/beehave/libs/windows/~beehave.windows.editor.x86_64.dll b/addons/beehave/libs/windows/~beehave.windows.editor.x86_64.dll new file mode 100644 index 00000000..8ec5f252 Binary files /dev/null and b/addons/beehave/libs/windows/~beehave.windows.editor.x86_64.dll differ diff --git a/addons/beehave/plugin.gd b/addons/beehave/plugin.gd index ad315974..e674691a 100644 --- a/addons/beehave/plugin.gd +++ b/addons/beehave/plugin.gd @@ -4,4 +4,4 @@ extends EditorPlugin func _init(): name = "BeehavePlugin" - print("Beehave initialized!") \ No newline at end of file + print("Beehave initialized!") diff --git a/addons/gdUnit4/GdUnitRunner.cfg b/addons/gdUnit4/GdUnitRunner.cfg index 59dd7687..ff8bae30 100644 --- a/addons/gdUnit4/GdUnitRunner.cfg +++ b/addons/gdUnit4/GdUnitRunner.cfg @@ -1 +1 @@ -{"included":{"res://test/":[]},"server_port":31002,"skipped":{},"version":"1.0"} \ No newline at end of file +{"included":{"res://test/nodes/decorators/cooldown_test.gd":[]},"server_port":31002,"skipped":{},"version":"1.0"} \ No newline at end of file diff --git a/addons/gdUnit4/bin/GdUnitBuildTool.gd b/addons/gdUnit4/bin/GdUnitBuildTool.gd index 21eeb5a1..70709920 100644 --- a/addons/gdUnit4/bin/GdUnitBuildTool.gd +++ b/addons/gdUnit4/bin/GdUnitBuildTool.gd @@ -2,7 +2,9 @@ extends SceneTree enum { - INIT, PROCESSING, EXIT + INIT, + PROCESSING, + EXIT } const RETURN_SUCCESS = 0 @@ -11,9 +13,19 @@ const RETURN_WARNING = 101 var _console := CmdConsole.new() var _cmd_options: = CmdOptions.new([ - CmdOption.new("-scp, --src_class_path", "-scp ", "The full class path of the source file.", TYPE_STRING), - CmdOption.new("-scl, --src_class_line", "-scl ", "The selected line number to generate test case.", TYPE_INT) - ]) + CmdOption.new( + "-scp, --src_class_path", + "-scp ", + "The full class path of the source file.", + TYPE_STRING + ), + CmdOption.new( + "-scl, --src_class_line", + "-scl ", + "The selected line number to generate test case.", + TYPE_INT + ) +]) var _status := INIT var _source_file :String = "" @@ -49,13 +61,11 @@ func _idle(_delta): var script := ResourceLoader.load(_source_file) as Script if script == null: exit(RETURN_ERROR, "Can't load source file %s!" % _source_file) - var result := GdUnitTestSuiteBuilder.create(script, _source_line) if result.is_error(): print_json_error(result.error_message()) exit(RETURN_ERROR, result.error_message()) return - _console.prints_color("Added testcase: %s" % result.value(), Color.CORNFLOWER_BLUE) print_json_result(result.value()) exit(RETURN_SUCCESS) @@ -86,7 +96,8 @@ func print_json_error(error :String) -> void: func show_options() -> void: _console.prints_color(" Usage:", Color.DARK_SALMON) _console.prints_color(" build -scp -scl ", Color.DARK_SALMON) - _console.prints_color("-- Options ---------------------------------------------------------------------------------------", Color.DARK_SALMON).new_line() + _console.prints_color("-- Options ---------------------------------------------------------------------------------------", + Color.DARK_SALMON).new_line() for option in _cmd_options.default_options(): descripe_option(option) diff --git a/addons/gdUnit4/bin/GdUnitCmdTool.gd b/addons/gdUnit4/bin/GdUnitCmdTool.gd index 6ab26f88..00d5ad95 100644 --- a/addons/gdUnit4/bin/GdUnitCmdTool.gd +++ b/addons/gdUnit4/bin/GdUnitCmdTool.gd @@ -3,9 +3,11 @@ extends SceneTree const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd") + #warning-ignore-all:return_value_discarded -class CLIRunner extends Node: - +class CLIRunner: + extends Node + enum { READY, INIT, @@ -13,61 +15,111 @@ class CLIRunner extends Node: STOP, EXIT } - + const DEFAULT_REPORT_COUNT = 20 - const RETURN_SUCCESS = 0 - const RETURN_ERROR = 100 - const RETURN_ERROR_HEADLESS_NOT_SUPPORTED = 103 - const RETURN_WARNING = 101 + const RETURN_SUCCESS = 0 + const RETURN_ERROR = 100 + const RETURN_ERROR_HEADLESS_NOT_SUPPORTED = 103 + const RETURN_ERROR_GODOT_VERSION_NOT_SUPPORTED = 104 + const RETURN_WARNING = 101 var _state = READY - var _test_suites_to_process :Array + var _test_suites_to_process: Array var _executor var _cs_executor - var _report :GdUnitHtmlReport + var _report: GdUnitHtmlReport var _report_dir: String var _report_max: int = DEFAULT_REPORT_COUNT var _runner_config := GdUnitRunnerConfig.new() var _console := CmdConsole.new() - var _cmd_options: = CmdOptions.new([ - CmdOption.new("-a, --add", "-a ", "Adds the given test suite or directory to the execution pipeline.", TYPE_STRING), - CmdOption.new("-i, --ignore", "-i ", "Adds the given test suite or test case to the ignore list.", TYPE_STRING), - CmdOption.new("-c, --continue", "", "By default GdUnit will abort checked first test failure to be fail fast, instead of stop after first failure you can use this option to run the complete test set."), - CmdOption.new("-conf, --config", "-conf [testconfiguration.cfg]", "Run all tests by given test configuration. Default is 'GdUnitRunner.cfg'", TYPE_STRING, true), - CmdOption.new("-help", "", "Shows this help message."), - CmdOption.new("--help-advanced", "", "Shows advanced options.") - ], [ + var _cmd_options := CmdOptions.new([ + CmdOption.new( + "-a, --add", + "-a ", + "Adds the given test suite or directory to the execution pipeline.", + TYPE_STRING + ), + CmdOption.new( + "-i, --ignore", + "-i ", + "Adds the given test suite or test case to the ignore list.", + TYPE_STRING + ), + CmdOption.new( + "-c, --continue", + "", + """By default GdUnit will abort checked first test failure to be fail fast, + instead of stop after first failure you can use this option to run the complete test set.""".dedent() + ), + CmdOption.new( + "-conf, --config", + "-conf [testconfiguration.cfg]", + "Run all tests by given test configuration. Default is 'GdUnitRunner.cfg'", + TYPE_STRING, + true + ), + CmdOption.new( + "-help", "", + "Shows this help message." + ), + CmdOption.new("--help-advanced", "", + "Shows advanced options." + ) + ], + [ # advanced options - CmdOption.new("-rd, --report-directory", "-rd ", "Specifies the output directory in which the reports are to be written. The default is res://reports/.", TYPE_STRING, true), - CmdOption.new("-rc, --report-count", "-rc ", "Specifies how many reports are saved before they are deleted. The default is "+str(DEFAULT_REPORT_COUNT)+".", TYPE_INT, true), + CmdOption.new( + "-rd, --report-directory", + "-rd ", + "Specifies the output directory in which the reports are to be written. The default is res://reports/.", + TYPE_STRING, + true + ), + CmdOption.new( + "-rc, --report-count", + "-rc ", + "Specifies how many reports are saved before they are deleted. The default is %s." % str(DEFAULT_REPORT_COUNT), + TYPE_INT, + true + ), #CmdOption.new("--list-suites", "--list-suites [directory]", "Lists all test suites located in the given directory.", TYPE_STRING), #CmdOption.new("--describe-suite", "--describe-suite ", "Shows the description of selected test suite.", TYPE_STRING), - CmdOption.new("--info", "", "Shows the GdUnit version info"), - CmdOption.new("--selftest", "", "Runs the GdUnit self test"), + CmdOption.new( + "--info", "", + "Shows the GdUnit version info" + ), + CmdOption.new( + "--selftest", "", + "Runs the GdUnit self test" + ) ]) - - + + func _ready(): _state = INIT - _report_dir = GdUnitTools.current_dir() + "reports" + _report_dir = GdUnitFileAccess.current_dir() + "reports" _executor = load("res://addons/gdUnit4/src/core/execution/GdUnitTestSuiteExecutor.gd").new() # stop checked first test failure to fail fast _executor.fail_fast(true) - - if GdUnit4MonoApiLoader.is_mono_supported(): - prints("GdUnit4Mono Version %s loaded." % GdUnit4MonoApiLoader.version()) - _cs_executor = GdUnit4MonoApiLoader.create_executor(self) - var err = GdUnitSignals.instance().gdunit_event.connect(Callable(self, "_on_gdunit_event")) + if GdUnit4CSharpApiLoader.is_mono_supported(): + prints("GdUnit4Mono Version %s loaded." % GdUnit4CSharpApiLoader.version()) + _cs_executor = GdUnit4CSharpApiLoader.create_executor(self) + var err := GdUnitSignals.instance().gdunit_event.connect(_on_gdunit_event) if err != OK: prints("gdUnitSignals failed") push_error("Error checked startup, can't connect executor for 'send_event'") quit(RETURN_ERROR) - - - func _process(_delta): + + + func _notification(what: int) -> void: + if what == NOTIFICATION_PREDELETE: + prints("Finallize .. done") + + + func _process(_delta :float) -> void: match _state: INIT: - gdUnitInit() + init_gd_unit() _state = RUN RUN: # all test suites executed @@ -87,98 +139,151 @@ class CLIRunner extends Node: _state = EXIT _on_gdunit_event(GdUnitStop.new()) quit(report_exit_code(_report)) - - - func quit(code :int) -> void: + + + func quit(code: int) -> void: GdUnitTools.dispose_all() await GdUnitMemoryObserver.gc_on_guarded_instances() await get_tree().physics_frame get_tree().quit(code) - - - func set_report_dir(path :String) -> void: - _report_dir = ProjectSettings.globalize_path(GdUnitTools.make_qualified_path(path)) - _console.prints_color("Set write reports to %s" % _report_dir, Color.DEEP_SKY_BLUE) - - - func set_report_count(count :String) -> void: + + + func set_report_dir(path: String) -> void: + _report_dir = ProjectSettings.globalize_path(GdUnitFileAccess.make_qualified_path(path)) + _console.prints_color( + "Set write reports to %s" % _report_dir, + Color.DEEP_SKY_BLUE + ) + + + func set_report_count(count: String) -> void: var report_count := count.to_int() if report_count < 1: - _console.prints_error("Invalid report history count '%s' set back to default %d" % [count, DEFAULT_REPORT_COUNT]) + _console.prints_error( + "Invalid report history count '%s' set back to default %d" + % [count, DEFAULT_REPORT_COUNT] + ) _report_max = DEFAULT_REPORT_COUNT else: - _console.prints_color("Set report history count to %s" % count, Color.DEEP_SKY_BLUE) + _console.prints_color( + "Set report history count to %s" % count, + Color.DEEP_SKY_BLUE + ) _report_max = report_count - - + + func disable_fail_fast() -> void: - _console.prints_color("Disabled fail fast!", Color.DEEP_SKY_BLUE) + _console.prints_color( + "Disabled fail fast!", + Color.DEEP_SKY_BLUE + ) _executor.fail_fast(false) - - + + func run_self_test() -> void: - _console.prints_color("Run GdUnit4 self tests.", Color.DEEP_SKY_BLUE) + _console.prints_color( + "Run GdUnit4 self tests.", + Color.DEEP_SKY_BLUE + ) disable_fail_fast() _runner_config.self_test() - - + + func show_version() -> void: - _console.prints_color("Godot %s" % Engine.get_version_info().get("string"), Color.DARK_SALMON) - var config = ConfigFile.new() - config.load('addons/gdUnit4/plugin.cfg') - _console.prints_color("GdUnit4 %s" % config.get_value('plugin', 'version'), Color.DARK_SALMON) + _console.prints_color( + "Godot %s" % Engine.get_version_info().get("string"), + Color.DARK_SALMON + ) + var config := ConfigFile.new() + config.load("addons/gdUnit4/plugin.cfg") + _console.prints_color( + "GdUnit4 %s" % config.get_value("plugin", "version"), + Color.DARK_SALMON + ) quit(RETURN_SUCCESS) - - - func show_options(show_advanced :bool = false) -> void: - _console.prints_color(" Usage:", Color.DARK_SALMON) - _console.prints_color(" runtest -a ", Color.DARK_SALMON) - _console.prints_color(" runtest -a -i ", Color.DARK_SALMON).new_line() - _console.prints_color("-- Options ---------------------------------------------------------------------------------------", Color.DARK_SALMON).new_line() + + + func show_options(show_advanced: bool = false) -> void: + _console.prints_color( + """ + Usage: + runtest -a + runtest -a -i + """.dedent(), + Color.DARK_SALMON + ).prints_color( + "-- Options ---------------------------------------------------------------------------------------", + Color.DARK_SALMON + ).new_line() for option in _cmd_options.default_options(): descripe_option(option) if show_advanced: - _console.prints_color("-- Advanced options --------------------------------------------------------------------------", Color.DARK_SALMON).new_line() + _console.prints_color( + "-- Advanced options --------------------------------------------------------------------------", + Color.DARK_SALMON + ).new_line() for option in _cmd_options.advanced_options(): descripe_option(option) - - - func descripe_option(cmd_option :CmdOption) -> void: - _console.print_color(" %-40s" % str(cmd_option.commands()), Color.CORNFLOWER_BLUE) - _console.prints_color(cmd_option.description(), Color.LIGHT_GREEN) + + + func descripe_option(cmd_option: CmdOption) -> void: + _console.print_color( + " %-40s" % str(cmd_option.commands()), + Color.CORNFLOWER_BLUE + ) + _console.prints_color( + cmd_option.description(), + Color.LIGHT_GREEN + ) if not cmd_option.help().is_empty(): - _console.prints_color("%-4s %s" % ["", cmd_option.help()], Color.DARK_TURQUOISE) + _console.prints_color( + "%-4s %s" % ["", cmd_option.help()], + Color.DARK_TURQUOISE + ) _console.new_line() - - + + func load_test_config(path := GdUnitRunnerConfig.CONFIG_FILE) -> void: - _console.print_color("Loading test configuration %s\n" % path, Color.CORNFLOWER_BLUE) + _console.print_color( + "Loading test configuration %s\n" % path, + Color.CORNFLOWER_BLUE + ) _runner_config.load_config(path) - - + + func show_help() -> void: show_options() quit(RETURN_SUCCESS) - - + + func show_advanced_help() -> void: show_options(true) quit(RETURN_SUCCESS) - - - func gdUnitInit() -> void: - _console.prints_color("----------------------------------------------------------------------------------------------", Color.DARK_SALMON) - _console.prints_color(" GdUnit4 Comandline Tool", Color.DARK_SALMON) - _console.new_line() - + + + func init_gd_unit() -> void: + _console.prints_color( + """ + -------------------------------------------------------------------------------------------------- + GdUnit4 Comandline Tool + --------------------------------------------------------------------------------------------------""".dedent(), + Color.DARK_SALMON + ).new_line() + if DisplayServer.get_name() == "headless": - _console.prints_error("Headless mode is not supported!").new_line() - _console.print_color("Tests that use UI interaction do not work in headless mode because 'InputEvents' are not transported by the Godot engine and thus have no effect!", Color.CORNFLOWER_BLUE)\ - .new_line().new_line() - _console.prints_error("Abnormal exit with %d" % RETURN_ERROR_HEADLESS_NOT_SUPPORTED) + _console.prints_error( + "Headless mode is not supported!" + ).print_color(""" + Tests that use UI interaction do not work in headless mode because 'InputEvents' are not transported + by the Godot engine and thus have no effect! + """.dedent(), + Color.CORNFLOWER_BLUE + ).prints_error( + "Abnormal exit with %d" % RETURN_ERROR_HEADLESS_NOT_SUPPORTED + ) quit(RETURN_ERROR_HEADLESS_NOT_SUPPORTED) return - + var cmd_parser := CmdArgumentParser.new(_cmd_options, "GdUnitCmdTool.gd") var result := cmd_parser.parse(OS.get_cmdline_args()) if result.is_error(): @@ -188,57 +293,56 @@ class CLIRunner extends Node: _state = STOP quit(RETURN_ERROR) return - if result.is_empty(): show_help() return - # build runner config by given commands - result = CmdCommandHandler.new(_cmd_options)\ - .register_cb("-help", Callable(self, "show_help"))\ - .register_cb("--help-advanced", Callable(self, "show_advanced_help"))\ - .register_cb("-a", Callable(_runner_config, "add_test_suite"))\ - .register_cbv("-a", Callable(_runner_config, "add_test_suites"))\ - .register_cb("-i", Callable(_runner_config, "skip_test_suite"))\ - .register_cbv("-i", Callable(_runner_config, "skip_test_suites"))\ - .register_cb("-rd", Callable(self, "set_report_dir"))\ - .register_cb("-rc", Callable(self, "set_report_count"))\ - .register_cb("--selftest", Callable(self, "run_self_test"))\ - .register_cb("-c", Callable(self, "disable_fail_fast"))\ - .register_cb("-conf", Callable(self, "load_test_config"))\ - .register_cb("--info", Callable(self, "show_version"))\ - .execute(result.value()) + result = ( + CmdCommandHandler.new(_cmd_options) + .register_cb("-help", Callable(self, "show_help")) + .register_cb("--help-advanced", Callable(self, "show_advanced_help")) + .register_cb("-a", Callable(_runner_config, "add_test_suite")) + .register_cbv("-a", Callable(_runner_config, "add_test_suites")) + .register_cb("-i", Callable(_runner_config, "skip_test_suite")) + .register_cbv("-i", Callable(_runner_config, "skip_test_suites")) + .register_cb("-rd", Callable(self, "set_report_dir")) + .register_cb("-rc", Callable(self, "set_report_count")) + .register_cb("--selftest", Callable(self, "run_self_test")) + .register_cb("-c", Callable(self, "disable_fail_fast")) + .register_cb("-conf", Callable(self, "load_test_config")) + .register_cb("--info", Callable(self, "show_version")) + .execute(result.value()) + ) if result.is_error(): _console.prints_error(result.error_message()) _state = STOP quit(RETURN_ERROR) - + _test_suites_to_process = load_testsuites(_runner_config) if _test_suites_to_process.is_empty(): _console.prints_warning("No test suites found, abort test run!") - _console.prints_color("Exit code: %d" % RETURN_SUCCESS, Color.DARK_SALMON) + _console.prints_color("Exit code: %d" % RETURN_SUCCESS, Color.DARK_SALMON) _state = STOP quit(RETURN_SUCCESS) - - var total_test_count = _collect_test_case_count(_test_suites_to_process) + var total_test_count := _collect_test_case_count(_test_suites_to_process) _on_gdunit_event(GdUnitInit.new(_test_suites_to_process.size(), total_test_count)) - - - func load_testsuites(config :GdUnitRunnerConfig) -> Array[Node]: - var test_suites_to_process :Array[Node] = [] + + + func load_testsuites(config: GdUnitRunnerConfig) -> Array[Node]: + var test_suites_to_process: Array[Node] = [] var to_execute := config.to_execute() # scan for the requested test suites - var _scanner := GdUnitTestSuiteScanner.new() - for resource_path_ in to_execute.keys(): - var selected_tests :PackedStringArray = to_execute.get(resource_path_) - var scaned_suites := _scanner.scan(resource_path_) + var ts_scanner := GdUnitTestSuiteScanner.new() + for as_resource_path in to_execute.keys(): + var selected_tests: PackedStringArray = to_execute.get(as_resource_path) + var scaned_suites := ts_scanner.scan(as_resource_path) skip_test_case(scaned_suites, selected_tests) test_suites_to_process.append_array(scaned_suites) skip_suites(test_suites_to_process, config) return test_suites_to_process - - - func skip_test_case(test_suites :Array, test_case_names :Array) -> void: + + + func skip_test_case(test_suites: Array, test_case_names: Array) -> void: if test_case_names.is_empty(): return for test_suite in test_suites: @@ -246,15 +350,15 @@ class CLIRunner extends Node: if not test_case_names.has(test_case.get_name()): test_suite.remove_child(test_case) test_case.free() - - - func skip_suites(test_suites :Array, config :GdUnitRunnerConfig) -> void: + + + func skip_suites(test_suites: Array, config: GdUnitRunnerConfig) -> void: var skipped := config.skipped() for test_suite in test_suites: skip_suite(test_suite, skipped) - - - func skip_suite(test_suite :Node, skipped :Dictionary) -> void: + + + func skip_suite(test_suite: Node, skipped: Dictionary) -> void: var skipped_suites := skipped.keys() if skipped_suites.is_empty(): return @@ -262,11 +366,17 @@ class CLIRunner extends Node: # skipp c# testsuites for now if test_suite.get_script() == null: return - var test_suite_path :String = test_suite.get_meta("ResourcePath") if test_suite.get_script() == null else test_suite.get_script().resource_path + var test_suite_path: String = ( + test_suite.get_meta("ResourcePath") if test_suite.get_script() == null + else test_suite.get_script().resource_path + ) for suite_to_skip in skipped_suites: # if suite skipped by path or name - if suite_to_skip == test_suite_path or (suite_to_skip.is_valid_filename() and suite_to_skip == suite_name): - var skipped_tests :Array = skipped.get(suite_to_skip) + if ( + suite_to_skip == test_suite_path + or (suite_to_skip.is_valid_filename() and suite_to_skip == suite_name) + ): + var skipped_tests: Array = skipped.get(suite_to_skip) # if no tests skipped test the complete suite is skipped if skipped_tests.is_empty(): _console.prints_warning("Skip test suite %s:%s" % suite_to_skip) @@ -274,26 +384,30 @@ class CLIRunner extends Node: else: # skip tests for test_to_skip in skipped_tests: - var test_case :_TestCase = test_suite.find_child(test_to_skip, true, false) + var test_case: _TestCase = test_suite.find_child(test_to_skip, true, false) if test_case: test_case.skip(true) _console.prints_warning("Skip test case %s:%s" % [suite_to_skip, test_to_skip]) else: - _console.prints_error("Can't skip test '%s' checked test suite '%s', no test with given name exists!" % [test_to_skip, suite_to_skip]) - - - func _collect_test_case_count(testSuites :Array) -> int: - var total :int = 0 - for test_suite in testSuites: + _console.prints_error( + "Can't skip test '%s' checked test suite '%s', no test with given name exists!" + % [test_to_skip, suite_to_skip] + ) + + + func _collect_test_case_count(test_suites: Array) -> int: + var total: int = 0 + for test_suite in test_suites: total += (test_suite as Node).get_child_count() return total - - - func PublishEvent(data :Dictionary) -> void: + + + # gdlint: disable=function-name + func PublishEvent(data: Dictionary) -> void: _on_gdunit_event(GdUnitEvent.new().deserialize(data)) - - - func _on_gdunit_event(event :GdUnitEvent): + + + func _on_gdunit_event(event: GdUnitEvent): match event.type(): GdUnitEvent.INIT: _report = GdUnitHtmlReport.new(_report_dir) @@ -301,12 +415,23 @@ class CLIRunner extends Node: var report_path := _report.write() _report.delete_history(_report_max) JUnitXmlReport.new(_report._report_path, _report.iteration()).write(_report) - _console.prints_color("Total test suites: %s" % _report.suite_count(), Color.DARK_SALMON) - _console.prints_color("Total test cases: %s" % _report.test_count(), Color.DARK_SALMON) - _console.prints_color("Total time: %s" % LocalTime.elapsed(_report.duration()), Color.DARK_SALMON) - _console.prints_color("Open Report at: file://%s" % report_path, Color.CORNFLOWER_BLUE) + _console.prints_color( + "Total test suites: %s" % _report.suite_count(), + Color.DARK_SALMON + ).prints_color( + "Total test cases: %s" % _report.test_count(), + Color.DARK_SALMON + ).prints_color( + "Total time: %s" % LocalTime.elapsed(_report.duration()), + Color.DARK_SALMON + ).prints_color( + "Open Report at: file://%s" % report_path, + Color.CORNFLOWER_BLUE + ) GdUnitEvent.TESTSUITE_BEFORE: - _report.add_testsuite_report(GdUnitTestSuiteReport.new(event.resource_path(), event.suite_name())) + _report.add_testsuite_report( + GdUnitTestSuiteReport.new(event.resource_path(), event.suite_name()) + ) GdUnitEvent.TESTSUITE_AFTER: _report.update_test_suite_report( event.resource_path(), @@ -318,9 +443,17 @@ class CLIRunner extends Node: event.skipped_count(), event.failed_count(), event.orphan_nodes(), - event.reports()) + event.reports() + ) GdUnitEvent.TESTCASE_BEFORE: - _report.add_testcase_report(event.resource_path(), GdUnitTestCaseReport.new(event.resource_path(), event.suite_name(), event.test_name())) + _report.add_testcase_report( + event.resource_path(), + GdUnitTestCaseReport.new( + event.resource_path(), + event.suite_name(), + event.test_name() + ) + ) GdUnitEvent.TESTCASE_AFTER: var test_report := GdUnitTestCaseReport.new( event.resource_path(), @@ -332,72 +465,110 @@ class CLIRunner extends Node: event.orphan_nodes(), event.is_skipped(), event.reports(), - event.elapsed_time()) + event.elapsed_time() + ) _report.update_testcase_report(event.resource_path(), test_report) print_status(event) - - - func report_exit_code(report :GdUnitHtmlReport) -> int: + + + func report_exit_code(report: GdUnitHtmlReport) -> int: if report.error_count() + report.failure_count() > 0: _console.prints_color("Exit code: %d" % RETURN_ERROR, Color.FIREBRICK) return RETURN_ERROR if report.orphan_count() > 0: _console.prints_color("Exit code: %d" % RETURN_WARNING, Color.GOLDENROD) return RETURN_WARNING - _console.prints_color("Exit code: %d" % RETURN_SUCCESS, Color.DARK_SALMON) + _console.prints_color("Exit code: %d" % RETURN_SUCCESS, Color.DARK_SALMON) return RETURN_SUCCESS - - - func print_status(event :GdUnitEvent) -> void: + + + func print_status(event: GdUnitEvent) -> void: match event.type(): GdUnitEvent.TESTSUITE_BEFORE: - _console.prints_color("Run Test Suite %s " % event.resource_path(), Color.ANTIQUE_WHITE) + _console.prints_color( + "Run Test Suite %s " % event.resource_path(), + Color.ANTIQUE_WHITE + ) GdUnitEvent.TESTCASE_BEFORE: - _console.print_color(" Run Test: %s > %s :" % [event.resource_path(), event.test_name()], Color.ANTIQUE_WHITE)\ - .prints_color("STARTED", Color.FOREST_GREEN) + _console.print_color( + " Run Test: %s > %s :" % [event.resource_path(), event.test_name()], + Color.ANTIQUE_WHITE + ).prints_color( + "STARTED", + Color.FOREST_GREEN + ).save_cursor() GdUnitEvent.TESTCASE_AFTER: - _console.print_color(" Run Test: %s > %s :" % [event.resource_path(), event.test_name()], Color.ANTIQUE_WHITE) + #_console.restore_cursor() + _console.print_color( + " Run Test: %s > %s :" % [event.resource_path(), event.test_name()], + Color.ANTIQUE_WHITE + ) _print_status(event) _print_failure_report(event.reports()) GdUnitEvent.TESTSUITE_AFTER: _print_failure_report(event.reports()) _print_status(event) - _console.prints_color(" | %d total | %d error | %d failed | %d skipped | %d orphans |\n" % [_report.test_count(), _report.error_count(), _report.failure_count(), _report.skipped_count(), _report.orphan_count()], Color.ANTIQUE_WHITE) - - - func _print_failure_report(reports :Array) -> void: + _console.prints_color( + "Statistics: | %d tests cases | %d error | %d failed | %d skipped | %d orphans |\n" + % [ + _report.test_count(), + _report.error_count(), + _report.failure_count(), + _report.skipped_count(), + _report.orphan_count() + ], + Color.ANTIQUE_WHITE + ) + + + func _print_failure_report(reports: Array) -> void: for report in reports: - if report.is_failure() or report.is_error() or report.is_warning() or report.is_skipped(): - _console.prints_color(" Report:", Color.DARK_TURQUOISE, CmdConsole.BOLD|CmdConsole.UNDERLINE) - var text = GdUnitTools.richtext_normalize(report._to_string()) + if ( + report.is_failure() + or report.is_error() + or report.is_warning() + or report.is_skipped() + ): + _console.prints_color( + " Report:", + Color.DARK_TURQUOISE, CmdConsole.BOLD | CmdConsole.UNDERLINE + ) + var text = GdUnitTools.richtext_normalize(str(report)) for line in text.split("\n"): _console.prints_color(" %s" % line, Color.DARK_TURQUOISE) _console.new_line() - - - func _print_status(event :GdUnitEvent) -> void: + + + func _print_status(event: GdUnitEvent) -> void: if event.is_skipped(): - _console.print_color("SKIPPED", Color.GOLDENROD, CmdConsole.BOLD|CmdConsole.ITALIC) + _console.print_color("SKIPPED", Color.GOLDENROD, CmdConsole.BOLD | CmdConsole.ITALIC) elif event.is_failed() or event.is_error(): - _console.print_color("FAILED", Color.CRIMSON, CmdConsole.BOLD) + _console.print_color("FAILED", Color.FIREBRICK, CmdConsole.BOLD) elif event.orphan_nodes() > 0: - _console.print_color("PASSED", Color.GOLDENROD, CmdConsole.BOLD|CmdConsole.UNDERLINE) + _console.print_color("PASSED", Color.GOLDENROD, CmdConsole.BOLD | CmdConsole.UNDERLINE) else: _console.print_color("PASSED", Color.FOREST_GREEN, CmdConsole.BOLD) - _console.prints_color(" %s" % LocalTime.elapsed(event.elapsed_time()), Color.CORNFLOWER_BLUE) + _console.prints_color( + " %s" % LocalTime.elapsed(event.elapsed_time()), Color.CORNFLOWER_BLUE + ) -var _cli_runner :CLIRunner +var _cli_runner: CLIRunner func _initialize(): + if Engine.get_version_info().hex < 0x40100: + prints("GdUnit4 requires a minimum of Godot 4.1.x Version!") + quit(CLIRunner.RETURN_ERROR_GODOT_VERSION_NOT_SUPPORTED) + return DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_MINIMIZED) _cli_runner = CLIRunner.new() root.add_child(_cli_runner) -func _finalize(): - prints("Finallize ..") - prints("-Orphan nodes report-----------------------") - Window.print_orphan_nodes() - prints("Finallize .. done") +# do not use print statements on _finalize it results in random crashes +#func _finalize(): +# prints("Finallize ..") +# prints("-Orphan nodes report-----------------------") +# Window.print_orphan_nodes() +# prints("Finallize .. done") diff --git a/addons/gdUnit4/bin/GdUnitCopyLog.gd b/addons/gdUnit4/bin/GdUnitCopyLog.gd index 5f57806f..f7bb475b 100644 --- a/addons/gdUnit4/bin/GdUnitCopyLog.gd +++ b/addons/gdUnit4/bin/GdUnitCopyLog.gd @@ -3,6 +3,7 @@ extends MainLoop const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd") +# gdlint: disable=max-line-length const NO_LOG_TEMPLATE = """ @@ -23,14 +24,22 @@ const NO_LOG_TEMPLATE = """ """ #warning-ignore-all:return_value_discarded -var _cmd_options: = CmdOptions.new([ - CmdOption.new("-rd, --report-directory", "-rd ", "Specifies the output directory in which the reports are to be written. The default is res://reports/.", TYPE_STRING, true), -]) +var _cmd_options := CmdOptions.new([ + CmdOption.new( + "-rd, --report-directory", + "-rd ", + "Specifies the output directory in which the reports are to be written. The default is res://reports/.", + TYPE_STRING, + true + ) + ]) + +var _report_root_path: String -var _report_root_path :String func _init(): - _report_root_path = GdUnitTools.current_dir() + "reports" + _report_root_path = GdUnitFileAccess.current_dir() + "reports" + func _process(_delta): # check if reports exists @@ -38,81 +47,95 @@ func _process(_delta): prints("no reports found") return true # scan for latest report path - var iteration = GdUnitTools.find_last_path_index(_report_root_path, GdUnitHtmlReport.REPORT_DIR_PREFIX) + var iteration := GdUnitFileAccess.find_last_path_index( + _report_root_path, GdUnitHtmlReport.REPORT_DIR_PREFIX + ) var report_path = "%s/%s%d" % [_report_root_path, GdUnitHtmlReport.REPORT_DIR_PREFIX, iteration] - # only process if godot logging is enabled if not GdUnitSettings.is_log_enabled(): _patch_report(report_path, "") return true - - # parse possible custom report path, + # parse possible custom report path, var cmd_parser := CmdArgumentParser.new(_cmd_options, "GdUnitCmdTool.gd") # ignore erros and exit quitly if cmd_parser.parse(OS.get_cmdline_args(), true).is_error(): return true - CmdCommandHandler.new(_cmd_options).register_cb("-rd", Callable(self, "set_report_directory")) - + CmdCommandHandler.new(_cmd_options).register_cb("-rd", set_report_directory) # scan for latest godot log and copy to report var godot_log := _scan_latest_godot_log() var result := _copy_and_pach(godot_log, report_path) if result.is_error(): push_error(result.error_message()) return true - _patch_report(report_path, godot_log) return true -func set_report_directory(path :String) -> void: + +func set_report_directory(path: String) -> void: _report_root_path = path + func _scan_latest_godot_log() -> String: var path := GdUnitSettings.get_log_path().get_base_dir() var files_sorted := Array() - for file in GdUnitTools.scan_dir(path): - var file_name := "%s/%s" % [path,file] + for file in GdUnitFileAccess.scan_dir(path): + var file_name := "%s/%s" % [path, file] files_sorted.append(file_name) # sort by name, the name contains the timestamp so we sort at the end by timestamp files_sorted.sort() return files_sorted[-1] -func _patch_report(report_path :String, godot_log :String) -> void: + +func _patch_report(report_path: String, godot_log: String) -> void: var index_file := FileAccess.open("%s/index.html" % report_path, FileAccess.READ_WRITE) if index_file == null: - push_error("Can't add log path to index.html. Error: %s" % GdUnitTools.error_as_string(FileAccess.get_open_error())) + push_error( + "Can't add log path to index.html. Error: %s" + % error_string(FileAccess.get_open_error()) + ) return # if no log file available than add a information howto enable it if godot_log.is_empty(): - FileAccess.open("%s/logging_not_available.html" % report_path, FileAccess.WRITE)\ - .store_string(NO_LOG_TEMPLATE) + FileAccess.open( + "%s/logging_not_available.html" % report_path, + FileAccess.WRITE).store_string(NO_LOG_TEMPLATE) var log_file = "logging_not_available.html" if godot_log.is_empty() else godot_log.get_file() var content := index_file.get_as_text().replace("${log_file}", log_file) # overide it index_file.seek(0) index_file.store_string(content) - + + func _copy_and_pach(from_file: String, to_dir: String) -> GdUnitResult: - var result := GdUnitTools.copy_file(from_file, to_dir) + var result := GdUnitFileAccess.copy_file(from_file, to_dir) if result.is_error(): return result var file := FileAccess.open(from_file, FileAccess.READ) if file == null: - return GdUnitResult.error("Can't find file '%s'. Error: %s" % [from_file, GdUnitTools.error_as_string(FileAccess.get_open_error())]) + return GdUnitResult.error( + "Can't find file '%s'. Error: %s" + % [from_file, error_string(FileAccess.get_open_error())] + ) var content := file.get_as_text() # patch out console format codes for color_index in range(0, 256): var to_replace := "[38;5;%dm" % color_index content = content.replace(to_replace, "") - content = content.replace("", "")\ + content = content\ + .replace("", "")\ .replace(CmdConsole.__CSI_BOLD, "")\ .replace(CmdConsole.__CSI_ITALIC, "")\ .replace(CmdConsole.__CSI_UNDERLINE, "") var to_file := to_dir + "/" + from_file.get_file() file = FileAccess.open(to_file, FileAccess.WRITE) if file == null: - return GdUnitResult.error("Can't open to write '%s'. Error: %s" % [to_file, GdUnitTools.error_as_string(FileAccess.get_open_error())]) + return GdUnitResult.error( + "Can't open to write '%s'. Error: %s" + % [to_file, error_string(FileAccess.get_open_error())] + ) file.store_string(content) return GdUnitResult.empty() + func reports_available() -> bool: return DirAccess.dir_exists_absolute(_report_root_path) diff --git a/addons/gdUnit4/bin/ProjectScanner.gd b/addons/gdUnit4/bin/ProjectScanner.gd index 74bbbcf4..c125d1bb 100644 --- a/addons/gdUnit4/bin/ProjectScanner.gd +++ b/addons/gdUnit4/bin/ProjectScanner.gd @@ -4,87 +4,96 @@ extends SceneTree const CmdConsole = preload("res://addons/gdUnit4/src/cmd/CmdConsole.gd") -var scanner := SourceScanner.new() func _initialize(): set_auto_accept_quit(false) + var scanner := SourceScanner.new(self) root.add_child(scanner) -func _finalize(): - prints("__finalize") - - - +# gdlint: disable=trailing-whitespace class SourceScanner extends Node: enum { INIT, + STARTUP, SCAN, QUIT, DONE } - - var _counter = 0 - var WAIT_TIME_IN_MS = 5.000 var _state = INIT var _console := CmdConsole.new() + var _elapsed_time := 0.0 + var _plugin: EditorPlugin + var _fs: EditorFileSystem + var _scene: SceneTree - func _init(): - _state = SCAN + func _init(scene :SceneTree) -> void: + _scene = scene + _console.prints_color(""" + ======================================================================== + Running project scan:""".dedent(), + Color.CORNFLOWER_BLUE + ) + _state = INIT - func _process(delta): - if _state != SCAN: - return - _counter += delta - if _state == SCAN: - set_process(false) - _console.prints_color("======================================", Color.CORNFLOWER_BLUE) - _console.prints_color("Running project scan:", Color.CORNFLOWER_BLUE) - await scan_project() - set_process(true) - _state = QUIT - if _state == QUIT or _counter >= WAIT_TIME_IN_MS: - _state = DONE - _console.prints_color("Scan project done.", Color.CORNFLOWER_BLUE) - _console.prints_color("======================================", Color.CORNFLOWER_BLUE) - _console.new_line() - await get_tree().process_frame - get_tree().quit(0) + func _process(delta :float) -> void: + _elapsed_time += delta + set_process(false) + await_inital_scan() + await scan_project() + set_process(true) - func scan_project() -> void: - var plugin := EditorPlugin.new() - var fs := plugin.get_editor_interface().get_resource_filesystem() + # !! don't use any await in this phase otherwise the editor will be instable !! + func await_inital_scan() -> void: + if _state == INIT: + _console.prints_color("Wait initial scanning ...", Color.DARK_GREEN) + _plugin = EditorPlugin.new() + _fs = _plugin.get_editor_interface().get_resource_filesystem() + _plugin.get_editor_interface().set_plugin_enabled("gdUnit4", false) + _state = STARTUP - if fs.has_method("reimport_files--"): - _console.prints_color("Reimport images :", Color.SANDY_BROWN) - for source in ["res://addons/gdUnit4/src/ui/assets/orphan", "res://addons/gdUnit4/src/ui/assets/spinner", "res://addons/gdUnit4/src/ui/assets/"]: - var image_files := Array(DirAccess.get_files_at(source)) - #_console.prints_color("%s" % image_files, Color.SANDY_BROWN) - var files := image_files.map(func full_path(file_name): - return "%s/%s" % [source, file_name] )\ - .filter(func filter_import_files(path :String): - return path.get_extension() != "import") - prints(files) - fs.reimport_files(files) - - _console.prints_color("Scan sources: ", Color.SANDY_BROWN) - fs.scan_sources() - await get_tree().create_timer(5).timeout - await get_tree().process_frame + if _state == STARTUP: + if _fs.is_scanning(): + _console.progressBar(_fs.get_scanning_progress() * 100 as int) + # we wait 10s in addition to be on the save site the scanning is done + if _elapsed_time > 10.0: + _console.progressBar(100) + _console.new_line() + _console.prints_color("initial scanning ... done", Color.DARK_GREEN) + _state = SCAN + + func scan_project() -> void: + if _state != SCAN: + return + _console.prints_color("Scan project: ", Color.SANDY_BROWN) + await get_tree().process_frame + _fs.scan_sources() + await get_tree().create_timer(5).timeout _console.prints_color("Scan: ", Color.SANDY_BROWN) - fs.scan() + _console.progressBar(0) await get_tree().process_frame - while fs.is_scanning(): + _fs.scan() + while _fs.is_scanning(): await get_tree().process_frame - _console.progressBar(fs.get_scanning_progress() * 100 as int) + _console.progressBar(_fs.get_scanning_progress() * 100 as int) + await get_tree().create_timer(10).timeout _console.progressBar(100) _console.new_line() + _plugin.free() + _console.prints_color(""" + Scan project done. + ========================================================================""".dedent(), + Color.CORNFLOWER_BLUE + ) await get_tree().process_frame - plugin.queue_free() - await get_tree().process_frame + await get_tree().physics_frame + queue_free() + # force quit editor + _state = DONE + _scene.quit(0) diff --git a/addons/gdUnit4/plugin.cfg b/addons/gdUnit4/plugin.cfg index 56c4c0f6..90c99c20 100644 --- a/addons/gdUnit4/plugin.cfg +++ b/addons/gdUnit4/plugin.cfg @@ -3,5 +3,5 @@ name="gdUnit4" description="Unit Testing Framework for Godot Scripts" author="Mike Schulze" -version="4.2.0" +version="4.2.1" script="plugin.gd" diff --git a/addons/gdUnit4/plugin.gd b/addons/gdUnit4/plugin.gd index 065e0318..963673ef 100644 --- a/addons/gdUnit4/plugin.gd +++ b/addons/gdUnit4/plugin.gd @@ -4,11 +4,14 @@ extends EditorPlugin const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd") var _gd_inspector :Node -var _server_node +var _server_node :Node var _gd_console :Node -func _enter_tree(): +func _enter_tree() -> void: + if Engine.get_version_info().hex < 0x40100: + prints("GdUnit4 plugin requires a minimum of Godot 4.1.x Version!") + return Engine.set_meta("GdUnitEditorPlugin", self) GdUnitSettings.setup() # install the GdUnit inspector @@ -21,13 +24,13 @@ func _enter_tree(): add_child(_server_node) prints("Loading GdUnit4 Plugin success") if GdUnitSettings.is_update_notification_enabled(): - var update_tool = load("res://addons/gdUnit4/src/update/GdUnitUpdateNotify.tscn").instantiate() + var update_tool :Node = load("res://addons/gdUnit4/src/update/GdUnitUpdateNotify.tscn").instantiate() Engine.get_main_loop().root.call_deferred("add_child", update_tool) - if GdUnit4MonoApiLoader.is_mono_supported(): - prints("GdUnit4Mono Version %s loaded." % GdUnit4MonoApiLoader.version()) + if GdUnit4CSharpApiLoader.is_mono_supported(): + prints("GdUnit4Mono Version %s loaded." % GdUnit4CSharpApiLoader.version()) -func _exit_tree(): +func _exit_tree() -> void: if is_instance_valid(_gd_inspector): remove_control_from_docks(_gd_inspector) _gd_inspector.free() diff --git a/addons/gdUnit4/runtest.sh b/addons/gdUnit4/runtest.sh index c1e55bd1..20980a97 100644 --- a/addons/gdUnit4/runtest.sh +++ b/addons/gdUnit4/runtest.sh @@ -8,5 +8,8 @@ fi $GODOT_BIN --path . -s -d ./addons/gdUnit4/bin/GdUnitCmdTool.gd $* exit_code=$? +echo "Run tests ends with $exit_code" + $GODOT_BIN --headless --path . --quiet -s -d ./addons/gdUnit4/bin/GdUnitCopyLog.gd $* > /dev/null +exit_code2=$? exit $exit_code diff --git a/addons/gdUnit4/src/GdUnitAssert.gd b/addons/gdUnit4/src/GdUnitAssert.gd index 04ba5263..1674d26c 100644 --- a/addons/gdUnit4/src/GdUnitAssert.gd +++ b/addons/gdUnit4/src/GdUnitAssert.gd @@ -3,32 +3,6 @@ class_name GdUnitAssert extends RefCounted -# Scans the current stack trace for the root cause to extract the line number -static func _get_line_number() -> int: - var stack_trace := get_stack() - if stack_trace == null or stack_trace.is_empty(): - return -1 - for stack_info in stack_trace: - var function :String = stack_info.get("function") - # we catch helper asserts to skip over to return the correct line number - if function.begins_with("assert_"): - continue - if function.begins_with("test_"): - return stack_info.get("line") - var source :String = stack_info.get("source") - if source.is_empty() \ - or source.begins_with("user://") \ - or source.ends_with("GdUnitAssert.gd") \ - or source.ends_with("AssertImpl.gd") \ - or source.ends_with("GdUnitTestSuite.gd") \ - or source.ends_with("GdUnitSceneRunnerImpl.gd") \ - or source.ends_with("GdUnitObjectInteractions.gd") \ - or source.ends_with("GdUnitAwaiter.gd"): - continue - return stack_info.get("line") - return -1 - - ## Verifies that the current value is null. func is_null(): return self diff --git a/addons/gdUnit4/src/GdUnitAwaiter.gd b/addons/gdUnit4/src/GdUnitAwaiter.gd index c1fc2ccf..cc6bf3fb 100644 --- a/addons/gdUnit4/src/GdUnitAwaiter.gd +++ b/addons/gdUnit4/src/GdUnitAwaiter.gd @@ -11,7 +11,7 @@ const GdUnitAssertImpl = preload("res://addons/gdUnit4/src/asserts/GdUnitAssertI # timeout: the timeout in ms, default is set to 2000ms func await_signal_on(source :Object, signal_name :String, args :Array = [], timeout_millis :int = 2000) -> Variant: # fail fast if the given source instance invalid - var line_number := GdUnitAssert._get_line_number() + var line_number := GdUnitAssertions.get_line_number() if not is_instance_valid(source): GdUnitAssertImpl.new(signal_name)\ .report_error(GdAssertMessages.error_await_signal_on_invalid_instance(source, signal_name, args), line_number) @@ -35,7 +35,7 @@ func await_signal_on(source :Object, signal_name :String, args :Array = [], time # args: the expected signal arguments as an array # timeout: the timeout in ms, default is set to 2000ms func await_signal_idle_frames(source :Object, signal_name :String, args :Array = [], timeout_millis :int = 2000) -> Variant: - var line_number := GdUnitAssert._get_line_number() + var line_number := GdUnitAssertions.get_line_number() # fail fast if the given source instance invalid if not is_instance_valid(source): GdUnitAssertImpl.new(signal_name)\ diff --git a/addons/gdUnit4/src/GdUnitSceneRunner.gd b/addons/gdUnit4/src/GdUnitSceneRunner.gd index 62c2dc90..9601c899 100644 --- a/addons/gdUnit4/src/GdUnitSceneRunner.gd +++ b/addons/gdUnit4/src/GdUnitSceneRunner.gd @@ -56,10 +56,21 @@ func simulate_mouse_move(pos :Vector2) -> GdUnitSceneRunner: ## Simulates a mouse move to the relative coordinates (offset).[br] -## [member relative] : The relative position, e.g. the mouse position offset[br] -## [member speed] : The mouse speed in pixels per second.[br] +## [member relative] : The relative position, indicating the mouse position offset.[br] +## [member time] : The time to move the mouse by the relative position in seconds (default is 1 second).[br] +## [member trans_type] : Sets the type of transition used (default is TRANS_LINEAR).[br] @warning_ignore("unused_parameter") -func simulate_mouse_move_relative(relative :Vector2, speed :Vector2 = Vector2.ONE) -> GdUnitSceneRunner: +func simulate_mouse_move_relative(relative: Vector2, time: float = 1.0, trans_type: Tween.TransitionType = Tween.TRANS_LINEAR) -> GdUnitSceneRunner: + await Engine.get_main_loop().process_frame + return self + + +## Simulates a mouse move to the absolute coordinates.[br] +## [member position] : The final position of the mouse.[br] +## [member time] : The time to move the mouse to the final position in seconds (default is 1 second).[br] +## [member trans_type] : Sets the type of transition used (default is TRANS_LINEAR).[br] +@warning_ignore("unused_parameter") +func simulate_mouse_move_absolute(position: Vector2, time: float = 1.0, trans_type: Tween.TransitionType = Tween.TRANS_LINEAR) -> GdUnitSceneRunner: await Engine.get_main_loop().process_frame return self diff --git a/addons/gdUnit4/src/GdUnitTestSuite.gd b/addons/gdUnit4/src/GdUnitTestSuite.gd index 30734918..7b4a691d 100644 --- a/addons/gdUnit4/src/GdUnitTestSuite.gd +++ b/addons/gdUnit4/src/GdUnitTestSuite.gd @@ -24,7 +24,7 @@ var __skip_reason :String = "Unknow." var __active_test_case :String var __awaiter := __gdunit_awaiter() # holds the actual execution context -var __execution_context +var __execution_context :RefCounted ### We now load all used asserts and tool scripts into the cache according to the principle of "lazy loading" @@ -43,15 +43,19 @@ func __gdunit_tools() -> GDScript: return __lazy_load("res://addons/gdUnit4/src/core/GdUnitTools.gd") +func __gdunit_file_access() -> GDScript: + return __lazy_load("res://addons/gdUnit4/src/core/GdUnitFileAccess.gd") + + func __gdunit_awaiter() -> Object: return __lazy_load("res://addons/gdUnit4/src/GdUnitAwaiter.gd").new() -func __gdunit_argument_matchers(): +func __gdunit_argument_matchers() -> GDScript: return __lazy_load("res://addons/gdUnit4/src/matchers/GdUnitArgumentMatchers.gd") -func __gdunit_object_interactions(): +func __gdunit_object_interactions() -> GDScript: return __lazy_load("res://addons/gdUnit4/src/core/GdUnitObjectInteractions.gd") @@ -91,11 +95,11 @@ func set_active_test_case(test_case :String) -> void: # Mapps Godot error number to a readable error message. See at ERROR # https://docs.godotengine.org/de/stable/classes/class_@globalscope.html#enum-globalscope-error func error_as_string(error_number :int) -> String: - return __gdunit_tools().error_as_string(error_number) + return error_string(error_number) ## A litle helper to auto freeing your created objects after test execution -func auto_free(obj) -> Variant: +func auto_free(obj :Variant) -> Variant: return __execution_context.register_auto_free(obj) @@ -117,35 +121,35 @@ func discard_error_interupted_by_timeout() -> void: ## Useful for storing data during test execution. [br] ## The directory is automatically deleted after test suite execution func create_temp_dir(relative_path :String) -> String: - return __gdunit_tools().create_temp_dir(relative_path) + return __gdunit_file_access().create_temp_dir(relative_path) ## Deletes the temporary base directory[br] ## Is called automatically after each execution of the test suite -func clean_temp_dir(): - __gdunit_tools().clear_tmp() +func clean_temp_dir() -> void: + __gdunit_file_access().clear_tmp() ## Creates a new file under the temporary directory *user://tmp* + [br] ## with given name and given file (default = File.WRITE)[br] ## If success the returned File is automatically closed after the execution of the test suite func create_temp_file(relative_path :String, file_name :String, mode := FileAccess.WRITE) -> FileAccess: - return __gdunit_tools().create_temp_file(relative_path, file_name, mode) + return __gdunit_file_access().create_temp_file(relative_path, file_name, mode) ## Reads a resource by given path into a PackedStringArray. func resource_as_array(resource_path :String) -> PackedStringArray: - return __gdunit_tools().resource_as_array(resource_path) + return __gdunit_file_access().resource_as_array(resource_path) ## Reads a resource by given path and returned the content as String. func resource_as_string(resource_path :String) -> String: - return __gdunit_tools().resource_as_string(resource_path) + return __gdunit_file_access().resource_as_string(resource_path) ## Reads a resource by given path and return Variand translated by str_to_var -func resource_as_var(resource_path :String): - return str_to_var(__gdunit_tools().resource_as_string(resource_path)) +func resource_as_var(resource_path :String) -> Variant: + return str_to_var(__gdunit_file_access().resource_as_string(resource_path)) ## clears the debuger error list[br] @@ -164,7 +168,7 @@ func await_signal_on(source :Object, signal_name :String, args :Array = [], time ## Waits until the next idle frame -func await_idle_frame(): +func await_idle_frame() -> void: await __awaiter.await_idle_frame() @@ -175,7 +179,7 @@ func await_idle_frame(): ## await await_millis(myNode, 100).completed ## [/codeblock][br] ## use this waiter and not `await get_tree().create_timer().timeout to prevent errors when a test case is timed out -func await_millis(timeout :int): +func await_millis(timeout :int) -> void: await __awaiter.await_millis(timeout) @@ -190,7 +194,7 @@ func await_millis(timeout :int): ## # or simply creates a runner by using the scene resource path ## var runner := scene_runner("res://foo/my_scne.tscn") ## [/codeblock] -func scene_runner(scene, verbose := false) -> GdUnitSceneRunner: +func scene_runner(scene :Variant, verbose := false) -> GdUnitSceneRunner: return auto_free(__lazy_load("res://addons/gdUnit4/src/core/GdUnitSceneRunnerImpl.gd").new(scene, verbose)) @@ -206,12 +210,12 @@ const RETURN_DEEP_STUB = GdUnitMock.RETURN_DEEP_STUB ## Creates a mock for given class name -func mock(clazz, mock_mode := RETURN_DEFAULTS) -> Object: +func mock(clazz :Variant, mock_mode := RETURN_DEFAULTS) -> Object: return __lazy_load("res://addons/gdUnit4/src/mocking/GdUnitMockBuilder.gd").build(clazz, mock_mode) ## Creates a spy checked given object instance -func spy(instance) -> Object: +func spy(instance :Variant) -> Object: return __lazy_load("res://addons/gdUnit4/src/spy/GdUnitSpyBuilder.gd").build(instance) @@ -221,27 +225,27 @@ func spy(instance) -> Object: ## # overrides the return value of myMock.is_selected() to false ## do_return(false).on(myMock).is_selected() ## [/codeblock] -func do_return(value) -> GdUnitMock: +func do_return(value :Variant) -> GdUnitMock: return GdUnitMock.new(value) ## Verifies certain behavior happened at least once or exact number of times -func verify(obj, times := 1): +func verify(obj :Variant, times := 1) -> Variant: return __gdunit_object_interactions().verify(obj, times) ## Verifies no interactions is happen checked this mock or spy -func verify_no_interactions(obj) -> GdUnitAssert: +func verify_no_interactions(obj :Variant) -> GdUnitAssert: return __gdunit_object_interactions().verify_no_interactions(obj) ## Verifies the given mock or spy has any unverified interaction. -func verify_no_more_interactions(obj) -> GdUnitAssert: +func verify_no_more_interactions(obj :Variant) -> GdUnitAssert: return __gdunit_object_interactions().verify_no_more_interactions(obj) ## Resets the saved function call counters checked a mock or spy -func reset(obj) -> void: +func reset(obj :Variant) -> void: __gdunit_object_interactions().reset(obj) @@ -363,16 +367,16 @@ func any_basis() -> GdUnitArgumentMatcher: return __gdunit_argument_matchers().by_type(TYPE_BASIS) -## Argument matcher to match any Transform3D value -func any_transform() -> GdUnitArgumentMatcher: - return __gdunit_argument_matchers().by_type(TYPE_TRANSFORM3D) - - ## Argument matcher to match any Transform2D value func any_transform_2d() -> GdUnitArgumentMatcher: return __gdunit_argument_matchers().by_type(TYPE_TRANSFORM2D) +## Argument matcher to match any Transform3D value +func any_transform_3d() -> GdUnitArgumentMatcher: + return __gdunit_argument_matchers().by_type(TYPE_TRANSFORM3D) + + ## Argument matcher to match any NodePath value func any_node_path() -> GdUnitArgumentMatcher: return __gdunit_argument_matchers().by_type(TYPE_NODE_PATH) @@ -399,37 +403,47 @@ func any_array() -> GdUnitArgumentMatcher: ## Argument matcher to match any PackedByteArray value -func any_pool_byte_array() -> GdUnitArgumentMatcher: +func any_packed_byte_array() -> GdUnitArgumentMatcher: return __gdunit_argument_matchers().by_type(TYPE_PACKED_BYTE_ARRAY) ## Argument matcher to match any PackedInt32Array value -func any_pool_int_array() -> GdUnitArgumentMatcher: +func any_packed_int32_array() -> GdUnitArgumentMatcher: return __gdunit_argument_matchers().by_type(TYPE_PACKED_INT32_ARRAY) +## Argument matcher to match any PackedInt64Array value +func any_packed_int64_array() -> GdUnitArgumentMatcher: + return __gdunit_argument_matchers().by_type(TYPE_PACKED_INT64_ARRAY) + + ## Argument matcher to match any PackedFloat32Array value -func any_pool_float_array() -> GdUnitArgumentMatcher: +func any_packed_float32_array() -> GdUnitArgumentMatcher: return __gdunit_argument_matchers().by_type(TYPE_PACKED_FLOAT32_ARRAY) +## Argument matcher to match any PackedFloat64Array value +func any_packed_float64_array() -> GdUnitArgumentMatcher: + return __gdunit_argument_matchers().by_type(TYPE_PACKED_FLOAT64_ARRAY) + + ## Argument matcher to match any PackedStringArray value -func any_pool_string_array() -> GdUnitArgumentMatcher: +func any_packed_string_array() -> GdUnitArgumentMatcher: return __gdunit_argument_matchers().by_type(TYPE_PACKED_STRING_ARRAY) ## Argument matcher to match any PackedVector2Array value -func any_pool_vector2_array() -> GdUnitArgumentMatcher: +func any_packed_vector2_array() -> GdUnitArgumentMatcher: return __gdunit_argument_matchers().by_type(TYPE_PACKED_VECTOR2_ARRAY) ## Argument matcher to match any PackedVector3Array value -func any_pool_vector3_array() -> GdUnitArgumentMatcher: +func any_packed_vector3_array() -> GdUnitArgumentMatcher: return __gdunit_argument_matchers().by_type(TYPE_PACKED_VECTOR3_ARRAY) ## Argument matcher to match any PackedColorArray value -func any_pool_color_array() -> GdUnitArgumentMatcher: +func any_packed_color_array() -> GdUnitArgumentMatcher: return __gdunit_argument_matchers().by_type(TYPE_PACKED_COLOR_ARRAY) @@ -462,7 +476,7 @@ func tuple(arg0 :Variant, ## The common assertion tool to verify values. ## It checks the given value by type to fit to the best assert -func assert_that(current) -> GdUnitAssert: +func assert_that(current :Variant) -> GdUnitAssert: match typeof(current): TYPE_BOOL: return assert_bool(current) @@ -487,22 +501,22 @@ func assert_that(current) -> GdUnitAssert: ## An assertion tool to verify boolean values. -func assert_bool(current) -> GdUnitBoolAssert: +func assert_bool(current :Variant) -> GdUnitBoolAssert: return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitBoolAssertImpl.gd").new(current) ## An assertion tool to verify String values. -func assert_str(current) -> GdUnitStringAssert: +func assert_str(current :Variant) -> GdUnitStringAssert: return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitStringAssertImpl.gd").new(current) ## An assertion tool to verify integer values. -func assert_int(current) -> GdUnitIntAssert: +func assert_int(current :Variant) -> GdUnitIntAssert: return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitIntAssertImpl.gd").new(current) ## An assertion tool to verify float values. -func assert_float(current) -> GdUnitFloatAssert: +func assert_float(current :Variant) -> GdUnitFloatAssert: return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitFloatAssertImpl.gd").new(current) @@ -512,31 +526,31 @@ func assert_float(current) -> GdUnitFloatAssert: ## [codeblock] ## assert_vector(Vector2(1.2, 1.000001)).is_equal(Vector2(1.2, 1.000001)) ## [/codeblock] -func assert_vector(current) -> GdUnitVectorAssert: +func assert_vector(current :Variant) -> GdUnitVectorAssert: return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitVectorAssertImpl.gd").new(current) ## An assertion tool to verify arrays. -func assert_array(current) -> GdUnitArrayAssert: +func assert_array(current :Variant) -> GdUnitArrayAssert: return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd").new(current) ## An assertion tool to verify dictionaries. -func assert_dict(current) -> GdUnitDictionaryAssert: +func assert_dict(current :Variant) -> GdUnitDictionaryAssert: return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitDictionaryAssertImpl.gd").new(current) ## An assertion tool to verify FileAccess. -func assert_file(current) -> GdUnitFileAssert: +func assert_file(current :Variant) -> GdUnitFileAssert: return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitFileAssertImpl.gd").new(current) ## An assertion tool to verify Objects. -func assert_object(current) -> GdUnitObjectAssert: +func assert_object(current :Variant) -> GdUnitObjectAssert: return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitObjectAssertImpl.gd").new(current) -func assert_result(current) -> GdUnitResultAssert: +func assert_result(current :Variant) -> GdUnitResultAssert: return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitResultAssertImpl.gd").new(current) @@ -588,11 +602,11 @@ func assert_error(current :Callable) -> GdUnitGodotErrorAssert: return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitGodotErrorAssertImpl.gd").new(current) -func assert_not_yet_implemented(): +func assert_not_yet_implemented() -> void: __gdunit_assert().new(null).test_fail() -func fail(message :String): +func fail(message :String) -> void: __gdunit_assert().new(null).report_error(message) diff --git a/addons/gdUnit4/src/GdUnitTuple.gd b/addons/gdUnit4/src/GdUnitTuple.gd index a6e3c41b..86c16d4f 100644 --- a/addons/gdUnit4/src/GdUnitTuple.gd +++ b/addons/gdUnit4/src/GdUnitTuple.gd @@ -16,7 +16,7 @@ func _init(arg0:Variant, arg6 :Variant=NO_ARG, arg7 :Variant=NO_ARG, arg8 :Variant=NO_ARG, - arg9 :Variant=NO_ARG): + arg9 :Variant=NO_ARG) -> void: __values = GdArrayTools.filter_value([arg0,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9], NO_ARG) @@ -24,5 +24,5 @@ func values() -> Array: return __values -func _to_string(): +func _to_string() -> String: return "tuple(%s)" % str(__values) diff --git a/addons/gdUnit4/src/asserts/CallBackValueProvider.gd b/addons/gdUnit4/src/asserts/CallBackValueProvider.gd index 0b4a72cf..27242e80 100644 --- a/addons/gdUnit4/src/asserts/CallBackValueProvider.gd +++ b/addons/gdUnit4/src/asserts/CallBackValueProvider.gd @@ -1,5 +1,5 @@ # a value provider unsing a callback to get `next` value from a certain function -class_name CallBackValueProvider +class_name CallBackValueProvider extends ValueProvider var _cb :Callable diff --git a/addons/gdUnit4/src/asserts/DefaultValueProvider.gd b/addons/gdUnit4/src/asserts/DefaultValueProvider.gd index aa35d8f0..036a482d 100644 --- a/addons/gdUnit4/src/asserts/DefaultValueProvider.gd +++ b/addons/gdUnit4/src/asserts/DefaultValueProvider.gd @@ -1,11 +1,12 @@ # default value provider, simple returns the initial value -class_name DefaultValueProvider +class_name DefaultValueProvider extends ValueProvider var _value func _init(value): _value = value - + + func get_value(): return _value diff --git a/addons/gdUnit4/src/asserts/GdAssertMessages.gd b/addons/gdUnit4/src/asserts/GdAssertMessages.gd index 42a8ceb5..3411f42f 100644 --- a/addons/gdUnit4/src/asserts/GdAssertMessages.gd +++ b/addons/gdUnit4/src/asserts/GdAssertMessages.gd @@ -8,7 +8,7 @@ const SUB_COLOR := Color(1, 0, 0, .3) const ADD_COLOR := Color(0, 1, 0, .3) -static func _format_dict(value :Dictionary) -> String: +static func format_dict(value :Dictionary) -> String: if value.is_empty(): return "{ }" var as_rows := var_to_str(value).split("\n") @@ -22,28 +22,30 @@ static func _format_dict(value :Dictionary) -> String: static func input_event_as_text(event :InputEvent) -> String: var text := "" if event is InputEventKey: - text += "InputEventKey : key='%s', pressed=%s, keycode=%d, physical_keycode=%s" % [event.as_text(), event.pressed, event.keycode, event.physical_keycode] + text += "InputEventKey : key='%s', pressed=%s, keycode=%d, physical_keycode=%s" % [ + event.as_text(), event.pressed, event.keycode, event.physical_keycode] else: text += event.as_text() if event is InputEventMouse: text += ", global_position %s" % event.global_position if event is InputEventWithModifiers: - text += ", shift=%s, alt=%s, control=%s, meta=%s, command=%s" % [event.shift_pressed, event.alt_pressed, event.ctrl_pressed, event.meta_pressed, event.command_or_control_autoremap] + text += ", shift=%s, alt=%s, control=%s, meta=%s, command=%s" % [ + event.shift_pressed, event.alt_pressed, event.ctrl_pressed, event.meta_pressed, event.command_or_control_autoremap] return text static func _colored_string_div(characters :String) -> String: - return _colored_array_div(characters.to_ascii_buffer()) + return colored_array_div(characters.to_ascii_buffer()) -static func _colored_array_div(characters :PackedByteArray) -> String: +static func colored_array_div(characters :PackedByteArray) -> String: if characters.is_empty(): return "" var result = PackedByteArray() var index = 0 var missing_chars := PackedByteArray() var additional_chars := PackedByteArray() - + while index < characters.size(): var character := characters[index] match character: @@ -62,7 +64,7 @@ static func _colored_array_div(characters :PackedByteArray) -> String: additional_chars = PackedByteArray() result.append(character) index += 1 - + result.append_array(format_chars(missing_chars, SUB_COLOR)) result.append_array(format_chars(additional_chars, ADD_COLOR)) return result.get_string_from_utf8() @@ -106,10 +108,10 @@ static func _colored_value(value, _delimiter ="\n") -> String: if value is InputEvent: return "[color=%s]<%s>[/color]" % [VALUE_COLOR, input_event_as_text(value)] if value.has_method("_to_string"): - return "[color=%s]<%s>[/color]" % [VALUE_COLOR, value._to_string()] + return "[color=%s]<%s>[/color]" % [VALUE_COLOR, str(value)] return "[color=%s]<%s>[/color]" % [VALUE_COLOR, value.get_class()] TYPE_DICTIONARY: - return "'[color=%s]%s[/color]'" % [VALUE_COLOR, _format_dict(value)] + return "'[color=%s]%s[/color]'" % [VALUE_COLOR, format_dict(value)] _: if GdArrayTools.is_array_type(value): return "'[color=%s]%s[/color]'" % [VALUE_COLOR, _typed_value(value)] @@ -146,9 +148,9 @@ static func orphan_detected_on_test(count :int): static func fuzzer_interuped(iterations: int, error: String) -> String: return "%s %s %s\n %s" % [ - _error("Found an error after"), - _colored_value(iterations + 1), - _error("test iterations"), + _error("Found an error after"), + _colored_value(iterations + 1), + _error("test iterations"), error] @@ -156,6 +158,7 @@ static func test_timeout(timeout :int) -> String: return "%s\n %s" % [_error("Timeout !"), _colored_value("Test timed out after %s" % LocalTime.elapsed(timeout))] +# gdlint:disable = mixed-tabs-and-spaces static func test_suite_skipped(hint :String, skip_count) -> String: return """ %s @@ -201,7 +204,8 @@ static func error_not_equal(current, expected) -> String: static func error_not_equal_case_insensetiv(current, expected) -> String: - return "%s\n %s\n not equal to (case insensitiv)\n %s" % [_error("Expecting:"), _colored_value(expected, true), _colored_value(current, true)] + return "%s\n %s\n not equal to (case insensitiv)\n %s" % [ + _error("Expecting:"), _colored_value(expected, true), _colored_value(current, true)] static func error_is_empty(current) -> String: @@ -217,7 +221,7 @@ static func error_is_same(current, expected) -> String: @warning_ignore("unused_parameter") -static func error_not_same(current, expected) -> String: +static func error_not_same(_current, expected) -> String: return "%s\n %s" % [_error("Expecting not same:"), _colored_value(expected)] @@ -285,9 +289,11 @@ static func error_is_value(operation, current, expected, expected2=null) -> Stri Comparator.GREATER_EQUAL: return "%s\n %s but was '%s'" % [_error("Expecting to be greater than or equal:"), _colored_value(expected), _nerror(current)] Comparator.BETWEEN_EQUAL: - return "%s\n %s\n in range between\n %s <> %s" % [_error("Expecting:"), _colored_value(current), _colored_value(expected), _colored_value(expected2)] + return "%s\n %s\n in range between\n %s <> %s" % [ + _error("Expecting:"), _colored_value(current), _colored_value(expected), _colored_value(expected2)] Comparator.NOT_BETWEEN_EQUAL: - return "%s\n %s\n not in range between\n %s <> %s" % [_error("Expecting:"), _colored_value(current), _colored_value(expected), _colored_value(expected2)] + return "%s\n %s\n not in range between\n %s <> %s" % [ + _error("Expecting:"), _colored_value(current), _colored_value(expected), _colored_value(expected2)] return "TODO create expected message" @@ -332,15 +338,23 @@ static func error_has_length(current, expected: int, compare_operator) -> String var current_length = current.length() if current != null else null match compare_operator: Comparator.EQUAL: - return "%s\n %s but was '%s' in\n %s" % [_error("Expecting size:"), _colored_value(expected), _nerror(current_length), _colored_value(current)] + return "%s\n %s but was '%s' in\n %s" % [ + _error("Expecting size:"), _colored_value(expected), _nerror(current_length), _colored_value(current)] Comparator.LESS_THAN: - return "%s\n %s but was '%s' in\n %s" % [_error("Expecting size to be less than:"), _colored_value(expected), _nerror(current_length), _colored_value(current)] + return "%s\n %s but was '%s' in\n %s" % [ + _error("Expecting size to be less than:"), _colored_value(expected), _nerror(current_length), _colored_value(current)] Comparator.LESS_EQUAL: - return "%s\n %s but was '%s' in\n %s" % [_error("Expecting size to be less than or equal:"), _colored_value(expected), _nerror(current_length), _colored_value(current)] + return "%s\n %s but was '%s' in\n %s" % [ + _error("Expecting size to be less than or equal:"), _colored_value(expected), + _nerror(current_length), _colored_value(current)] Comparator.GREATER_THAN: - return "%s\n %s but was '%s' in\n %s" % [_error("Expecting size to be greater than:"), _colored_value(expected), _nerror(current_length), _colored_value(current)] + return "%s\n %s but was '%s' in\n %s" % [ + _error("Expecting size to be greater than:"), _colored_value(expected), + _nerror(current_length), _colored_value(current)] Comparator.GREATER_EQUAL: - return "%s\n %s but was '%s' in\n %s" % [_error("Expecting size to be greater than or equal:"), _colored_value(expected), _nerror(current_length), _colored_value(current)] + return "%s\n %s but was '%s' in\n %s" % [ + _error("Expecting size to be greater than or equal:"), _colored_value(expected), + _nerror(current_length), _colored_value(current)] return "TODO create expected message" @@ -348,7 +362,8 @@ static func error_has_length(current, expected: int, compare_operator) -> String static func error_arr_contains(current, expected :Array, not_expect :Array, not_found :Array, by_reference :bool) -> String: var failure_message = "Expecting contains SAME elements:" if by_reference else "Expecting contains elements:" - var error := "%s\n %s\n do contains (in any order)\n %s" % [_error(failure_message), _colored_value(current, ", "), _colored_value(expected, ", ")] + var error := "%s\n %s\n do contains (in any order)\n %s" % [ + _error(failure_message), _colored_value(current, ", "), _colored_value(expected, ", ")] if not not_expect.is_empty(): error += "\nbut some elements where not expected:\n %s" % _colored_value(not_expect, ", ") if not not_found.is_empty(): @@ -358,12 +373,17 @@ static func error_arr_contains(current, expected :Array, not_expect :Array, not_ static func error_arr_contains_exactly(current, expected, not_expect, not_found, compare_mode :GdObjects.COMPARE_MODE) -> String: - var failure_message = "Expecting contains exactly elements:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST else "Expecting contains SAME exactly elements:" + var failure_message = ( + "Expecting contains exactly elements:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST + else "Expecting contains SAME exactly elements:" + ) if not_expect.is_empty() and not_found.is_empty(): var diff := _find_first_diff(current, expected) - return "%s\n %s\n do contains (in same order)\n %s\n but has different order %s" % [_error(failure_message), _colored_value(current, ", "), _colored_value(expected, ", "), diff] - - var error := "%s\n %s\n do contains (in same order)\n %s" % [_error(failure_message), _colored_value(current, ", "), _colored_value(expected, ", ")] + return "%s\n %s\n do contains (in same order)\n %s\n but has different order %s" % [ + _error(failure_message), _colored_value(current, ", "), _colored_value(expected, ", "), diff] + + var error := "%s\n %s\n do contains (in same order)\n %s" % [ + _error(failure_message), _colored_value(current, ", "), _colored_value(expected, ", ")] if not not_expect.is_empty(): error += "\nbut some elements where not expected:\n %s" % _colored_value(not_expect, ", ") if not not_found.is_empty(): @@ -372,9 +392,19 @@ static func error_arr_contains_exactly(current, expected, not_expect, not_found, return error -static func error_arr_contains_exactly_in_any_order(current, expected :Array, not_expect :Array, not_found :Array, compare_mode :GdObjects.COMPARE_MODE) -> String: - var failure_message = "Expecting contains exactly elements:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST else "Expecting contains SAME exactly elements:" - var error := "%s\n %s\n do contains exactly (in any order)\n %s" % [_error(failure_message), _colored_value(current, ", "), _colored_value(expected, ", ")] +static func error_arr_contains_exactly_in_any_order( + current, + expected :Array, + not_expect :Array, + not_found :Array, + compare_mode :GdObjects.COMPARE_MODE) -> String: + + var failure_message = ( + "Expecting contains exactly elements:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST + else "Expecting contains SAME exactly elements:" + ) + var error := "%s\n %s\n do contains exactly (in any order)\n %s" % [ + _error(failure_message), _colored_value(current, ", "), _colored_value(expected, ", ")] if not not_expect.is_empty(): error += "\nbut some elements where not expected:\n %s" % _colored_value(not_expect, ", ") if not not_found.is_empty(): @@ -385,7 +415,8 @@ static func error_arr_contains_exactly_in_any_order(current, expected :Array, no static func error_arr_not_contains(current :Array, expected :Array, found :Array, compare_mode :GdObjects.COMPARE_MODE) -> String: var failure_message = "Expecting:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST else "Expecting SAME:" - var error := "%s\n %s\n do not contains\n %s" % [_error(failure_message), _colored_value(current, ", "), _colored_value(expected, ", ")] + var error := "%s\n %s\n do not contains\n %s" % [ + _error(failure_message), _colored_value(current, ", "), _colored_value(expected, ", ")] if not found.is_empty(): error += "\n but found elements:\n %s" % _colored_value(found, ", ") return error @@ -393,18 +424,30 @@ static func error_arr_not_contains(current :Array, expected :Array, found :Array # - DictionaryAssert specific messages ---------------------------------------------- static func error_contains_keys(current :Array, expected :Array, keys_not_found :Array, compare_mode :GdObjects.COMPARE_MODE) -> String: - var failure := "Expecting contains keys:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST else "Expecting contains SAME keys:" - return "%s\n %s\n to contains:\n %s\n but can't find key's:\n %s" % [_error(failure), _colored_value(current, ", "), _colored_value(expected, ", "), _colored_value(keys_not_found, ", ")] + var failure := ( + "Expecting contains keys:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST + else "Expecting contains SAME keys:" + ) + return "%s\n %s\n to contains:\n %s\n but can't find key's:\n %s" % [ + _error(failure), _colored_value(current, ", "), _colored_value(expected, ", "), _colored_value(keys_not_found, ", ")] static func error_not_contains_keys(current :Array, expected :Array, keys_not_found :Array, compare_mode :GdObjects.COMPARE_MODE) -> String: - var failure := "Expecting NOT contains keys:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST else "Expecting NOT contains SAME keys" - return "%s\n %s\n do not contains:\n %s\n but contains key's:\n %s" % [_error(failure), _colored_value(current, ", "), _colored_value(expected, ", "), _colored_value(keys_not_found, ", ")] + var failure := ( + "Expecting NOT contains keys:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST + else "Expecting NOT contains SAME keys" + ) + return "%s\n %s\n do not contains:\n %s\n but contains key's:\n %s" % [ + _error(failure), _colored_value(current, ", "), _colored_value(expected, ", "), _colored_value(keys_not_found, ", ")] static func error_contains_key_value(key, value, current_value, compare_mode :GdObjects.COMPARE_MODE) -> String: - var failure := "Expecting contains key and value:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST else "Expecting contains SAME key and value:" - return "%s\n %s : %s\n but contains\n %s : %s" % [_error(failure), _colored_value(key), _colored_value(value), _colored_value(key), _colored_value(current_value)] + var failure := ( + "Expecting contains key and value:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST + else "Expecting contains SAME key and value:" + ) + return "%s\n %s : %s\n but contains\n %s : %s" % [ + _error(failure), _colored_value(key), _colored_value(value), _colored_value(key), _colored_value(current_value)] # - ResultAssert specific errors ---------------------------------------------------- @@ -454,17 +497,22 @@ static func error_interrupted(func_name :String, expected, elapsed :String) -> S static func error_wait_signal(signal_name :String, args :Array, elapsed :String) -> String: if args.is_empty(): - return "%s %s but timed out after %s" % [_error("Expecting emit signal:"), _colored_value(signal_name + "()"), elapsed] - return "%s %s but timed out after %s" % [_error("Expecting emit signal:"), _colored_value(signal_name + "(" + str(args) + ")"), elapsed] + return "%s %s but timed out after %s" % [ + _error("Expecting emit signal:"), _colored_value(signal_name + "()"), elapsed] + return "%s %s but timed out after %s" % [ + _error("Expecting emit signal:"), _colored_value(signal_name + "(" + str(args) + ")"), elapsed] static func error_signal_emitted(signal_name :String, args :Array, elapsed :String) -> String: if args.is_empty(): - return "%s %s but is emitted after %s" % [_error("Expecting do not emit signal:"), _colored_value(signal_name + "()"), elapsed] - return "%s %s but is emitted after %s" % [_error("Expecting do not emit signal:"), _colored_value(signal_name + "(" + str(args) + ")"), elapsed] + return "%s %s but is emitted after %s" % [ + _error("Expecting do not emit signal:"), _colored_value(signal_name + "()"), elapsed] + return "%s %s but is emitted after %s" % [ + _error("Expecting do not emit signal:"), _colored_value(signal_name + "(" + str(args) + ")"), elapsed] static func error_await_signal_on_invalid_instance(source, signal_name :String, args :Array) -> String: - return "%s\n await_signal_on(%s, %s, %s)" % [_error("Invalid source! Can't await on signal:"), _colored_value(source), signal_name, args] + return "%s\n await_signal_on(%s, %s, %s)" % [ + _error("Invalid source! Can't await on signal:"), _colored_value(source), signal_name, args] static func result_type(type :int) -> String: match type: @@ -499,7 +547,8 @@ static func error_validate_interactions(current_interactions :Dictionary, expect var times :int = current_interactions[args] interactions.append(_format_arguments(args, times)) var expected_interaction := _format_arguments(expected_interactions.keys()[0], expected_interactions.values()[0]) - return "%s\n%s\n%s\n%s" % [_error("Expecting interaction on:"), expected_interaction, _error("But found interactions on:"), "\n".join(interactions)] + return "%s\n%s\n%s\n%s" % [ + _error("Expecting interaction on:"), expected_interaction, _error("But found interactions on:"), "\n".join(interactions)] static func _format_arguments(args :Array, times :int) -> String: @@ -545,7 +594,8 @@ static func format_chars(characters :PackedByteArray, type :Color) -> PackedByte if characters.size() == 0:# or characters[0] == 10: return characters var result := PackedByteArray() - var message := "[bgcolor=#%s][color=with]%s[/color][/bgcolor]" % [type.to_html(), characters.get_string_from_utf8().replace("\n", "")] + var message := "[bgcolor=#%s][color=with]%s[/color][/bgcolor]" % [ + type.to_html(), characters.get_string_from_utf8().replace("\n", "")] result.append_array(message.to_utf8_buffer()) return result diff --git a/addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd index 419f0172..e488ac88 100644 --- a/addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd +++ b/addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd @@ -7,10 +7,11 @@ var _current_value_provider :ValueProvider func _init(current): _current_value_provider = DefaultValueProvider.new(current) - _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE).new(current) + _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", + ResourceLoader.CACHE_MODE_REUSE).new(current) # save the actual assert instance on the current thread context GdUnitThreadManager.get_current_context().set_assert(self) - if not __validate_value_type(current): + if not _validate_value_type(current): report_error("GdUnitArrayAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current)) @@ -30,7 +31,7 @@ func report_error(error :String) -> GdUnitArrayAssert: _base.report_error(error) return self -func _failure_message() -> String: +func failure_message() -> String: return _base._current_error_message @@ -39,14 +40,11 @@ func override_failure_message(message :String) -> GdUnitArrayAssert: return self -func __validate_value_type(value) -> bool: - return ( - value == null - or GdArrayTools.is_array_type(value) - ) +func _validate_value_type(value) -> bool: + return value == null or GdArrayTools.is_array_type(value) -func __current() -> Variant: +func current_value() -> Variant: return _current_value_provider.get_value() @@ -57,27 +55,27 @@ func max_length(left, right) -> int: func _array_equals_div(current, expected, case_sensitive :bool = false) -> Array: - var current_ := PackedStringArray(Array(current)) - var expected_ := PackedStringArray(Array(expected)) - var index_report_ := Array() - for index in current_.size(): - var c := current_[index] - if index < expected_.size(): - var e := expected_[index] + var current_value := PackedStringArray(Array(current)) + var expected_value := PackedStringArray(Array(expected)) + var index_report := Array() + for index in current_value.size(): + var c := current_value[index] + if index < expected_value.size(): + var e := expected_value[index] if not GdObjects.equals(c, e, case_sensitive): var length := max_length(c, e) - current_[index] = GdAssertMessages.format_invalid(c.lpad(length)) - expected_[index] = e.lpad(length) - index_report_.push_back({"index" : index, "current" :c, "expected": e}) + current_value[index] = GdAssertMessages.format_invalid(c.lpad(length)) + expected_value[index] = e.lpad(length) + index_report.push_back({"index" : index, "current" :c, "expected": e}) else: - current_[index] = GdAssertMessages.format_invalid(c) - index_report_.push_back({"index" : index, "current" :c, "expected": ""}) - - for index in range(current.size(), expected_.size()): - var value := expected_[index] - expected_[index] = GdAssertMessages.format_invalid(value) - index_report_.push_back({"index" : index, "current" : "", "expected": value}) - return [current_, expected_, index_report_] + current_value[index] = GdAssertMessages.format_invalid(c) + index_report.push_back({"index" : index, "current" :c, "expected": ""}) + + for index in range(current.size(), expected_value.size()): + var value := expected_value[index] + expected_value[index] = GdAssertMessages.format_invalid(value) + index_report.push_back({"index" : index, "current" : "", "expected": value}) + return [current_value, expected_value, index_report] func _array_div(compare_mode :GdObjects.COMPARE_MODE, left :Array[Variant], right :Array[Variant], _same_order := false) -> Array[Variant]: @@ -96,66 +94,66 @@ func _array_div(compare_mode :GdObjects.COMPARE_MODE, left :Array[Variant], righ func _contains(expected, compare_mode :GdObjects.COMPARE_MODE) -> GdUnitArrayAssert: - if not __validate_value_type(expected): + if not _validate_value_type(expected): return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected)) var by_reference := compare_mode == GdObjects.COMPARE_MODE.OBJECT_REFERENCE - var current_ = __current() - if current_ == null: - return report_error(GdAssertMessages.error_arr_contains(current_, expected, [], expected, by_reference)) - var diffs := _array_div(compare_mode, current_, expected) + var current_value = current_value() + if current_value == null: + return report_error(GdAssertMessages.error_arr_contains(current_value, expected, [], expected, by_reference)) + var diffs := _array_div(compare_mode, current_value, expected) #var not_expect := diffs[0] as Array var not_found := diffs[1] as Array if not not_found.is_empty(): - return report_error(GdAssertMessages.error_arr_contains(current_, expected, [], not_found, by_reference)) + return report_error(GdAssertMessages.error_arr_contains(current_value, expected, [], not_found, by_reference)) return report_success() func _contains_exactly(expected, compare_mode :GdObjects.COMPARE_MODE) -> GdUnitArrayAssert: - if not __validate_value_type(expected): + if not _validate_value_type(expected): return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected)) - var current_ = __current() - if current_ == null: - return report_error(GdAssertMessages.error_arr_contains_exactly(current_, expected, [], expected, compare_mode)) + var current_value = current_value() + if current_value == null: + return report_error(GdAssertMessages.error_arr_contains_exactly(current_value, expected, [], expected, compare_mode)) # has same content in same order - if GdObjects.equals(Array(current_), Array(expected), false, compare_mode): + if GdObjects.equals(Array(current_value), Array(expected), false, compare_mode): return report_success() # check has same elements but in different order - if GdObjects.equals_sorted(Array(current_), Array(expected), false, compare_mode): - return report_error(GdAssertMessages.error_arr_contains_exactly(current_, expected, [], [], compare_mode)) + if GdObjects.equals_sorted(Array(current_value), Array(expected), false, compare_mode): + return report_error(GdAssertMessages.error_arr_contains_exactly(current_value, expected, [], [], compare_mode)) # find the difference - var diffs := _array_div(compare_mode, current_, expected, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST) + var diffs := _array_div(compare_mode, current_value, expected, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST) var not_expect := diffs[0] as Array[Variant] var not_found := diffs[1] as Array[Variant] - return report_error(GdAssertMessages.error_arr_contains_exactly(current_, expected, not_expect, not_found, compare_mode)) + return report_error(GdAssertMessages.error_arr_contains_exactly(current_value, expected, not_expect, not_found, compare_mode)) func _contains_exactly_in_any_order(expected, compare_mode :GdObjects.COMPARE_MODE) -> GdUnitArrayAssert: - if not __validate_value_type(expected): + if not _validate_value_type(expected): return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected)) - var current_ = __current() - if current_ == null: - return report_error(GdAssertMessages.error_arr_contains_exactly_in_any_order(current_, expected, [], expected, compare_mode)) + var current_value = current_value() + if current_value == null: + return report_error(GdAssertMessages.error_arr_contains_exactly_in_any_order(current_value, expected, [], expected, compare_mode)) # find the difference - var diffs := _array_div(compare_mode, current_, expected, false) + var diffs := _array_div(compare_mode, current_value, expected, false) var not_expect := diffs[0] as Array var not_found := diffs[1] as Array if not_expect.is_empty() and not_found.is_empty(): return report_success() - return report_error(GdAssertMessages.error_arr_contains_exactly_in_any_order(current_, expected, not_expect, not_found, compare_mode)) + return report_error(GdAssertMessages.error_arr_contains_exactly_in_any_order(current_value, expected, not_expect, not_found, compare_mode)) func _not_contains(expected, compare_mode :GdObjects.COMPARE_MODE) -> GdUnitArrayAssert: - if not __validate_value_type(expected): + if not _validate_value_type(expected): return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected)) - var current_ = __current() - if current_ == null: - return report_error(GdAssertMessages.error_arr_contains_exactly_in_any_order(current_, expected, [], expected, compare_mode)) - var diffs := _array_div(compare_mode, current_, expected) + var current_value = current_value() + if current_value == null: + return report_error(GdAssertMessages.error_arr_contains_exactly_in_any_order(current_value, expected, [], expected, compare_mode)) + var diffs := _array_div(compare_mode, current_value, expected) var found := diffs[0] as Array - if found.size() == current_.size(): + if found.size() == current_value.size(): return report_success() var diffs2 := _array_div(compare_mode, expected, diffs[1]) - return report_error(GdAssertMessages.error_arr_not_contains(current_, expected, diffs2[0], compare_mode)) + return report_error(GdAssertMessages.error_arr_not_contains(current_value, expected, diffs2[0], compare_mode)) func is_null() -> GdUnitArrayAssert: @@ -170,13 +168,13 @@ func is_not_null() -> GdUnitArrayAssert: # Verifies that the current String is equal to the given one. func is_equal(expected) -> GdUnitArrayAssert: - if not __validate_value_type(expected): + if not _validate_value_type(expected): return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected)) - var current_ = __current() - if current_ == null and expected != null: + var current_value = current_value() + if current_value == null and expected != null: return report_error(GdAssertMessages.error_equal(null, expected)) - if not GdObjects.equals(current_, expected): - var diff := _array_equals_div(current_, expected) + if not GdObjects.equals(current_value, expected): + var diff := _array_equals_div(current_value, expected) var expected_as_list = GdArrayTools.as_string(diff[0], false) var current_as_list = GdArrayTools.as_string(diff[1], false) var index_report = diff[2] @@ -186,13 +184,13 @@ func is_equal(expected) -> GdUnitArrayAssert: # Verifies that the current Array is equal to the given one, ignoring case considerations. func is_equal_ignoring_case(expected) -> GdUnitArrayAssert: - if not __validate_value_type(expected): + if not _validate_value_type(expected): return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected)) - var current_ = __current() - if current_ == null and expected != null: + var current_value = current_value() + if current_value == null and expected != null: return report_error(GdAssertMessages.error_equal(null, GdArrayTools.as_string(expected))) - if not GdObjects.equals(current_, expected, true): - var diff := _array_equals_div(current_, expected, true) + if not GdObjects.equals(current_value, expected, true): + var diff := _array_equals_div(current_value, expected, true) var expected_as_list := GdArrayTools.as_string(diff[0]) var current_as_list := GdArrayTools.as_string(diff[1]) var index_report = diff[2] @@ -201,62 +199,62 @@ func is_equal_ignoring_case(expected) -> GdUnitArrayAssert: func is_not_equal(expected) -> GdUnitArrayAssert: - if not __validate_value_type(expected): + if not _validate_value_type(expected): return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected)) - var current_ = __current() - if GdObjects.equals(current_, expected): - return report_error(GdAssertMessages.error_not_equal(current_, expected)) + var current_value = current_value() + if GdObjects.equals(current_value, expected): + return report_error(GdAssertMessages.error_not_equal(current_value, expected)) return report_success() func is_not_equal_ignoring_case(expected) -> GdUnitArrayAssert: - if not __validate_value_type(expected): + if not _validate_value_type(expected): return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected)) - var current_ = __current() - if GdObjects.equals(current_, expected, true): - var c := GdArrayTools.as_string(current_) + var current_value = current_value() + if GdObjects.equals(current_value, expected, true): + var c := GdArrayTools.as_string(current_value) var e := GdArrayTools.as_string(expected) return report_error(GdAssertMessages.error_not_equal_case_insensetiv(c, e)) return report_success() func is_empty() -> GdUnitArrayAssert: - var current_ = __current() - if current_ == null or current_.size() > 0: - return report_error(GdAssertMessages.error_is_empty(current_)) + var current_value = current_value() + if current_value == null or current_value.size() > 0: + return report_error(GdAssertMessages.error_is_empty(current_value)) return report_success() func is_not_empty() -> GdUnitArrayAssert: - var current_ = __current() - if current_ != null and current_.size() == 0: + var current_value = current_value() + if current_value != null and current_value.size() == 0: return report_error(GdAssertMessages.error_is_not_empty()) return report_success() @warning_ignore("unused_parameter", "shadowed_global_identifier") func is_same(expected) -> GdUnitArrayAssert: - if not __validate_value_type(expected): + if not _validate_value_type(expected): return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected)) - var current = __current() + var current = current_value() if not is_same(current, expected): report_error(GdAssertMessages.error_is_same(current, expected)) return self func is_not_same(expected) -> GdUnitArrayAssert: - if not __validate_value_type(expected): + if not _validate_value_type(expected): return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected)) - var current = __current() + var current = current_value() if is_same(current, expected): report_error(GdAssertMessages.error_not_same(current, expected)) return self func has_size(expected: int) -> GdUnitArrayAssert: - var current_ = __current() - if current_ == null or current_.size() != expected: - return report_error(GdAssertMessages.error_has_size(current_, expected)) + var current_value = current_value() + if current_value == null or current_value.size() != expected: + return report_error(GdAssertMessages.error_has_size(current_value, expected)) return report_success() @@ -299,8 +297,9 @@ func is_instanceof(expected) -> GdUnitAssert: func extract(func_name :String, args := Array()) -> GdUnitArrayAssert: var extracted_elements := Array() - var extractor :GdUnitValueExtractor = ResourceLoader.load("res://addons/gdUnit4/src/extractors/GdUnitFuncValueExtractor.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE).new(func_name, args) - var current = __current() + var extractor :GdUnitValueExtractor = ResourceLoader.load("res://addons/gdUnit4/src/extractors/GdUnitFuncValueExtractor.gd", + "GDScript", ResourceLoader.CACHE_MODE_REUSE).new(func_name, args) + var current = current_value() if current == null: _current_value_provider = DefaultValueProvider.new(null) else: @@ -311,8 +310,8 @@ func extract(func_name :String, args := Array()) -> GdUnitArrayAssert: func extractv( - extr0 :GdUnitValueExtractor, - extr1 :GdUnitValueExtractor = null, + extr0 :GdUnitValueExtractor, + extr1 :GdUnitValueExtractor = null, extr2 :GdUnitValueExtractor = null, extr3 :GdUnitValueExtractor = null, extr4 :GdUnitValueExtractor = null, @@ -323,16 +322,26 @@ func extractv( extr9 :GdUnitValueExtractor = null) -> GdUnitArrayAssert: var extractors :Variant = GdArrayTools.filter_value([extr0, extr1, extr2, extr3, extr4, extr5, extr6, extr7, extr8, extr9], null) var extracted_elements := Array() - var current = __current() + var current = current_value() if current == null: _current_value_provider = DefaultValueProvider.new(null) else: - for element in __current(): - var ev :Array[Variant] = [GdUnitTuple.NO_ARG, GdUnitTuple.NO_ARG, GdUnitTuple.NO_ARG, GdUnitTuple.NO_ARG, GdUnitTuple.NO_ARG, GdUnitTuple.NO_ARG, GdUnitTuple.NO_ARG, GdUnitTuple.NO_ARG, GdUnitTuple.NO_ARG, GdUnitTuple.NO_ARG] + for element in current_value(): + var ev :Array[Variant] = [ + GdUnitTuple.NO_ARG, + GdUnitTuple.NO_ARG, + GdUnitTuple.NO_ARG, + GdUnitTuple.NO_ARG, + GdUnitTuple.NO_ARG, + GdUnitTuple.NO_ARG, + GdUnitTuple.NO_ARG, + GdUnitTuple.NO_ARG, + GdUnitTuple.NO_ARG, + GdUnitTuple.NO_ARG + ] for index in extractors.size(): var extractor :GdUnitValueExtractor = extractors[index] ev[index] = extractor.extract_value(element) - if extractors.size() > 1: extracted_elements.append(GdUnitTuple.new(ev[0], ev[1], ev[2], ev[3], ev[4], ev[5], ev[6], ev[7], ev[8], ev[9])) else: diff --git a/addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd index 6fc87b18..30694b0c 100644 --- a/addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd +++ b/addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd @@ -6,32 +6,28 @@ var _current_error_message :String = "" var _custom_failure_message :String = "" -func _init(current :Variant): +func _init(current :Variant) -> void: _current = current # save the actual assert instance on the current thread context GdUnitThreadManager.get_current_context().set_assert(self) GdAssertReports.reset_last_error_line_number() -func _failure_message() -> String: +func failure_message() -> String: return _current_error_message -func __current() -> Variant: +func current_value() -> Variant: return _current -func __validate_value_type(value, type :Variant.Type) -> bool: - return value == null or typeof(value) == type - - func report_success() -> GdUnitAssert: GdAssertReports.report_success() return self func report_error(error_message :String, failure_line_number: int = -1) -> GdUnitAssert: - var line_number := failure_line_number if failure_line_number != -1 else GdUnitAssert._get_line_number() + var line_number := failure_line_number if failure_line_number != -1 else GdUnitAssertions.get_line_number() GdAssertReports.set_last_error_line_number(line_number) _current_error_message = error_message if _custom_failure_message.is_empty() else _custom_failure_message GdAssertReports.report_error(_current_error_message, line_number) @@ -48,28 +44,28 @@ func override_failure_message(message :String): func is_equal(expected) -> GdUnitAssert: - var current = __current() + var current = current_value() if not GdObjects.equals(current, expected): return report_error(GdAssertMessages.error_equal(current, expected)) return report_success() func is_not_equal(expected) -> GdUnitAssert: - var current = __current() + var current = current_value() if GdObjects.equals(current, expected): return report_error(GdAssertMessages.error_not_equal(current, expected)) return report_success() func is_null() -> GdUnitAssert: - var current = __current() + var current = current_value() if current != null: return report_error(GdAssertMessages.error_is_null(current)) return report_success() func is_not_null() -> GdUnitAssert: - var current = __current() + var current = current_value() if current == null: return report_error(GdAssertMessages.error_is_not_null()) return report_success() diff --git a/addons/gdUnit4/src/asserts/GdUnitAssertions.gd b/addons/gdUnit4/src/asserts/GdUnitAssertions.gd index 0f7219b3..a4527911 100644 --- a/addons/gdUnit4/src/asserts/GdUnitAssertions.gd +++ b/addons/gdUnit4/src/asserts/GdUnitAssertions.gd @@ -3,8 +3,9 @@ class_name GdUnitAssertions extends RefCounted -func _init(): +func _init() -> void: # preload all gdunit assertions to speedup testsuite loading time + # gdlint:disable=private-method-call GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd") GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitBoolAssertImpl.gd") GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitStringAssertImpl.gd") @@ -26,5 +27,37 @@ func _init(): ### in order to noticeably reduce the loading time of the test suite. # We go this hard way to increase the loading performance to avoid reparsing all the used scripts # for more detailed info -> https://github.com/godotengine/godot/issues/67400 +# gdlint:disable=function-name static func __lazy_load(script_path :String) -> GDScript: return ResourceLoader.load(script_path, "GDScript", ResourceLoader.CACHE_MODE_REUSE) + + +static func validate_value_type(value, type :Variant.Type) -> bool: + return value == null or typeof(value) == type + +# Scans the current stack trace for the root cause to extract the line number +static func get_line_number() -> int: + var stack_trace := get_stack() + if stack_trace == null or stack_trace.is_empty(): + return -1 + for index in stack_trace.size(): + var stack_info :Dictionary = stack_trace[index] + var function :String = stack_info.get("function") + # we catch helper asserts to skip over to return the correct line number + if function.begins_with("assert_"): + continue + if function.begins_with("test_"): + return stack_info.get("line") + var source :String = stack_info.get("source") + if source.is_empty() \ + or source.begins_with("user://") \ + or source.ends_with("GdUnitAssert.gd") \ + or source.ends_with("GdUnitAssertions.gd") \ + or source.ends_with("AssertImpl.gd") \ + or source.ends_with("GdUnitTestSuite.gd") \ + or source.ends_with("GdUnitSceneRunnerImpl.gd") \ + or source.ends_with("GdUnitObjectInteractions.gd") \ + or source.ends_with("GdUnitAwaiter.gd"): + continue + return stack_info.get("line") + return -1 diff --git a/addons/gdUnit4/src/asserts/GdUnitBoolAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitBoolAssertImpl.gd index d838c86f..c2cdd34f 100644 --- a/addons/gdUnit4/src/asserts/GdUnitBoolAssertImpl.gd +++ b/addons/gdUnit4/src/asserts/GdUnitBoolAssertImpl.gd @@ -4,10 +4,11 @@ var _base: GdUnitAssert func _init(current): - _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE).new(current) + _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", + ResourceLoader.CACHE_MODE_REUSE).new(current) # save the actual assert instance on the current thread context GdUnitThreadManager.get_current_context().set_assert(self) - if not _base.__validate_value_type(current, TYPE_BOOL): + if not GdUnitAssertions.validate_value_type(current, TYPE_BOOL): report_error("GdUnitBoolAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current)) @@ -18,8 +19,8 @@ func _notification(event): _base = null -func __current(): - return _base.__current() +func current_value(): + return _base.current_value() func report_success() -> GdUnitBoolAssert: @@ -32,7 +33,7 @@ func report_error(error :String) -> GdUnitBoolAssert: return self -func _failure_message() -> String: +func failure_message() -> String: return _base._current_error_message @@ -64,12 +65,12 @@ func is_not_equal(expected) -> GdUnitBoolAssert: func is_true() -> GdUnitBoolAssert: - if __current() != true: - return report_error(GdAssertMessages.error_is_true(__current())) + if current_value() != true: + return report_error(GdAssertMessages.error_is_true(current_value())) return report_success() func is_false() -> GdUnitBoolAssert: - if __current() == true || __current() == null: - return report_error(GdAssertMessages.error_is_false(__current())) + if current_value() == true || current_value() == null: + return report_error(GdAssertMessages.error_is_false(current_value())) return report_success() diff --git a/addons/gdUnit4/src/asserts/GdUnitDictionaryAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitDictionaryAssertImpl.gd index 514894ba..1d9dab23 100644 --- a/addons/gdUnit4/src/asserts/GdUnitDictionaryAssertImpl.gd +++ b/addons/gdUnit4/src/asserts/GdUnitDictionaryAssertImpl.gd @@ -4,10 +4,11 @@ var _base :GdUnitAssert func _init(current): - _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE).new(current) + _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", + ResourceLoader.CACHE_MODE_REUSE).new(current) # save the actual assert instance on the current thread context GdUnitThreadManager.get_current_context().set_assert(self) - if not _base.__validate_value_type(current, TYPE_DICTIONARY): + if not GdUnitAssertions.validate_value_type(current, TYPE_DICTIONARY): report_error("GdUnitDictionaryAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current)) @@ -28,7 +29,7 @@ func report_error(error :String) -> GdUnitDictionaryAssert: return self -func _failure_message() -> String: +func failure_message() -> String: return _base._current_error_message @@ -37,8 +38,8 @@ func override_failure_message(message :String) -> GdUnitDictionaryAssert: return self -func __current() -> Variant: - return _base.__current() +func current_value() -> Variant: + return _base.current_value() func is_null() -> GdUnitDictionaryAssert: @@ -52,20 +53,20 @@ func is_not_null() -> GdUnitDictionaryAssert: func is_equal(expected) -> GdUnitDictionaryAssert: - var current = __current() + var current = current_value() if current == null: - return report_error(GdAssertMessages.error_equal(null, GdAssertMessages._format_dict(expected))) + return report_error(GdAssertMessages.error_equal(null, GdAssertMessages.format_dict(expected))) if not GdObjects.equals(current, expected): - var c := GdAssertMessages._format_dict(current) - var e := GdAssertMessages._format_dict(expected) + var c := GdAssertMessages.format_dict(current) + var e := GdAssertMessages.format_dict(expected) var diff := GdDiffTool.string_diff(c, e) - var curent_ = GdAssertMessages._colored_array_div(diff[1]) - return report_error(GdAssertMessages.error_equal(curent_, e)) + var curent_diff := GdAssertMessages.colored_array_div(diff[1]) + return report_error(GdAssertMessages.error_equal(curent_diff, e)) return report_success() func is_not_equal(expected) -> GdUnitDictionaryAssert: - var current = __current() + var current = current_value() if GdObjects.equals(current, expected): return report_error(GdAssertMessages.error_not_equal(current, expected)) return report_success() @@ -73,44 +74,42 @@ func is_not_equal(expected) -> GdUnitDictionaryAssert: @warning_ignore("unused_parameter", "shadowed_global_identifier") func is_same(expected) -> GdUnitDictionaryAssert: - var current = __current() + var current = current_value() if current == null: - return report_error(GdAssertMessages.error_equal(null, GdAssertMessages._format_dict(expected))) + return report_error(GdAssertMessages.error_equal(null, GdAssertMessages.format_dict(expected))) if not is_same(current, expected): - var c := GdAssertMessages._format_dict(current) - var e := GdAssertMessages._format_dict(expected) + var c := GdAssertMessages.format_dict(current) + var e := GdAssertMessages.format_dict(expected) var diff := GdDiffTool.string_diff(c, e) - var curent_ = GdAssertMessages._colored_array_div(diff[1]) - return report_error(GdAssertMessages.error_is_same(curent_, e)) + var curent_diff := GdAssertMessages.colored_array_div(diff[1]) + return report_error(GdAssertMessages.error_is_same(curent_diff, e)) return report_success() - @warning_ignore("unused_parameter", "shadowed_global_identifier") func is_not_same(expected) -> GdUnitDictionaryAssert: - var current = __current() + var current = current_value() if is_same(current, expected): return report_error(GdAssertMessages.error_not_same(current, expected)) return report_success() - func is_empty() -> GdUnitDictionaryAssert: - var current = __current() + var current = current_value() if current == null or not current.is_empty(): return report_error(GdAssertMessages.error_is_empty(current)) return report_success() func is_not_empty() -> GdUnitDictionaryAssert: - var current = __current() + var current = current_value() if current == null or current.is_empty(): return report_error(GdAssertMessages.error_is_not_empty()) return report_success() func has_size(expected: int) -> GdUnitDictionaryAssert: - var current = __current() + var current = current_value() if current == null: return report_error(GdAssertMessages.error_is_not_null()) if current.size() != expected: @@ -119,32 +118,22 @@ func has_size(expected: int) -> GdUnitDictionaryAssert: func _contains_keys(expected :Array, compare_mode :GdObjects.COMPARE_MODE) -> GdUnitDictionaryAssert: - var current = __current() + var current = current_value() if current == null: return report_error(GdAssertMessages.error_is_not_null()) # find expected keys - var keys_not_found :Array = expected.filter(func(expected_key): - for current_key in current.keys(): - if GdObjects.equals(current_key, expected_key, false, compare_mode): - return false - return true - ) + var keys_not_found :Array = expected.filter(_filter_by_key.bind(current.keys(), compare_mode)) if not keys_not_found.is_empty(): return report_error(GdAssertMessages.error_contains_keys(current.keys(), expected, keys_not_found, compare_mode)) return report_success() func _contains_key_value(key, value, compare_mode :GdObjects.COMPARE_MODE) -> GdUnitDictionaryAssert: - var current = __current() + var current = current_value() var expected := [key] if current == null: return report_error(GdAssertMessages.error_is_not_null()) - var keys_not_found :Array = expected.filter(func(expected_key): - for current_key in current.keys(): - if GdObjects.equals(current_key, expected_key, false, compare_mode): - return false - return true - ) + var keys_not_found :Array = expected.filter(_filter_by_key.bind(current.keys(), compare_mode)) if not keys_not_found.is_empty(): return report_error(GdAssertMessages.error_contains_key_value(key, value, current.keys(), compare_mode)) if not GdObjects.equals(current[key], value, false, compare_mode): @@ -153,15 +142,10 @@ func _contains_key_value(key, value, compare_mode :GdObjects.COMPARE_MODE) -> Gd func _not_contains_keys(expected :Array, compare_mode :GdObjects.COMPARE_MODE) -> GdUnitDictionaryAssert: - var current = __current() + var current = current_value() if current == null: return report_error(GdAssertMessages.error_is_not_null()) - var keys_found :Array = current.keys().filter(func(current_key): - for expected_key in expected: - if GdObjects.equals(current_key, expected_key, false, compare_mode): - return true - return false - ) + var keys_found :Array = current.keys().filter(_filter_by_key.bind(expected, compare_mode, true)) if not keys_found.is_empty(): return report_error(GdAssertMessages.error_not_contains_keys(current.keys(), expected, keys_found, compare_mode)) return report_success() @@ -189,3 +173,10 @@ func contains_same_key_value(key, value) -> GdUnitDictionaryAssert: func not_contains_same_keys(expected :Array) -> GdUnitDictionaryAssert: return _not_contains_keys(expected, GdObjects.COMPARE_MODE.OBJECT_REFERENCE) + + +func _filter_by_key(element :Variant, values :Array, compare_mode :GdObjects.COMPARE_MODE, is_not :bool = false) -> bool: + for key in values: + if GdObjects.equals(key, element, false, compare_mode): + return is_not + return !is_not diff --git a/addons/gdUnit4/src/asserts/GdUnitFailureAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitFailureAssertImpl.gd index 8be056e6..6b1cdaa6 100644 --- a/addons/gdUnit4/src/asserts/GdUnitFailureAssertImpl.gd +++ b/addons/gdUnit4/src/asserts/GdUnitFailureAssertImpl.gd @@ -24,7 +24,7 @@ func execute_and_await(assertion :Callable, do_await := true) -> GdUnitFailureAs _is_failed = true _failure_message = "Invalid Callable! It must be a callable of 'GdUnitAssert'" return - _failure_message = current_assert._failure_message() + _failure_message = current_assert.failure_message() return self @@ -38,12 +38,12 @@ func _on_test_failed(value :bool) -> void: @warning_ignore("unused_parameter") -func is_equal(expected :GdUnitAssert) -> GdUnitFailureAssert: +func is_equal(_expected :GdUnitAssert) -> GdUnitFailureAssert: return _report_error("Not implemented") @warning_ignore("unused_parameter") -func is_not_equal(expected :GdUnitAssert) -> GdUnitFailureAssert: +func is_not_equal(_expected :GdUnitAssert) -> GdUnitFailureAssert: return _report_error("Not implemented") @@ -79,7 +79,7 @@ func has_message(expected :String) -> GdUnitFailureAssert: var current_error := GdUnitTools.normalize_text(GdUnitTools.richtext_normalize(_failure_message)) if current_error != expected_error: var diffs := GdDiffTool.string_diff(current_error, expected_error) - var current := GdAssertMessages._colored_array_div(diffs[1]) + var current := GdAssertMessages.colored_array_div(diffs[1]) _report_error(GdAssertMessages.error_not_same_error(current, expected_error)) return self @@ -89,13 +89,13 @@ func starts_with_message(expected :String) -> GdUnitFailureAssert: var current_error := GdUnitTools.normalize_text(GdUnitTools.richtext_normalize(_failure_message)) if current_error.find(expected_error) != 0: var diffs := GdDiffTool.string_diff(current_error, expected_error) - var current := GdAssertMessages._colored_array_div(diffs[1]) + var current := GdAssertMessages.colored_array_div(diffs[1]) _report_error(GdAssertMessages.error_not_same_error(current, expected_error)) return self func _report_error(error_message :String, failure_line_number: int = -1) -> GdUnitAssert: - var line_number := failure_line_number if failure_line_number != -1 else GdUnitAssert._get_line_number() + var line_number := failure_line_number if failure_line_number != -1 else GdUnitAssertions.get_line_number() GdAssertReports.report_error(error_message, line_number) return self diff --git a/addons/gdUnit4/src/asserts/GdUnitFileAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitFileAssertImpl.gd index b4e07008..b23212a7 100644 --- a/addons/gdUnit4/src/asserts/GdUnitFileAssertImpl.gd +++ b/addons/gdUnit4/src/asserts/GdUnitFileAssertImpl.gd @@ -6,10 +6,11 @@ var _base: GdUnitAssert func _init(current): - _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE).new(current) + _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", + ResourceLoader.CACHE_MODE_REUSE).new(current) # save the actual assert instance on the current thread context GdUnitThreadManager.get_current_context().set_assert(self) - if not _base.__validate_value_type(current, TYPE_STRING): + if not GdUnitAssertions.validate_value_type(current, TYPE_STRING): report_error("GdUnitFileAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current)) @@ -20,8 +21,8 @@ func _notification(event): _base = null -func __current() -> String: - return _base.__current() as String +func current_value() -> String: + return _base.current_value() as String func report_success() -> GdUnitFileAssert: @@ -34,7 +35,7 @@ func report_error(error :String) -> GdUnitFileAssert: return self -func _failure_message() -> String: +func failure_message() -> String: return _base._current_error_message @@ -54,24 +55,24 @@ func is_not_equal(expected) -> GdUnitFileAssert: func is_file() -> GdUnitFileAssert: - var current := __current() + var current := current_value() if FileAccess.open(current, FileAccess.READ) == null: return report_error("Is not a file '%s', error code %s" % [current, FileAccess.get_open_error()]) return report_success() func exists() -> GdUnitFileAssert: - var current := __current() + var current := current_value() if not FileAccess.file_exists(current): return report_error("The file '%s' not exists" %current) return report_success() func is_script() -> GdUnitFileAssert: - var current := __current() + var current := current_value() if FileAccess.open(current, FileAccess.READ) == null: return report_error("Can't acces the file '%s'! Error code %s" % [current, FileAccess.get_open_error()]) - + var script = load(current) if not script is GDScript: return report_error("The file '%s' is not a GdScript" % current) @@ -79,16 +80,16 @@ func is_script() -> GdUnitFileAssert: func contains_exactly(expected_rows :Array) -> GdUnitFileAssert: - var current := __current() + var current := current_value() if FileAccess.open(current, FileAccess.READ) == null: return report_error("Can't acces the file '%s'! Error code %s" % [current, FileAccess.get_open_error()]) - + var script = load(current) if script is GDScript: var instance = script.new() var source_code = GdScriptParser.to_unix_format(instance.get_script().source_code) GdUnitTools.free_instance(instance) var rows := Array(source_code.split("\n")) - ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE).new(rows)\ - .contains_exactly(expected_rows) + ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd", "GDScript", + ResourceLoader.CACHE_MODE_REUSE).new(rows).contains_exactly(expected_rows) return self diff --git a/addons/gdUnit4/src/asserts/GdUnitFloatAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitFloatAssertImpl.gd index d457b00a..c0cd4b4d 100644 --- a/addons/gdUnit4/src/asserts/GdUnitFloatAssertImpl.gd +++ b/addons/gdUnit4/src/asserts/GdUnitFloatAssertImpl.gd @@ -4,10 +4,11 @@ var _base: GdUnitAssert func _init(current): - _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE).new(current) + _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", + ResourceLoader.CACHE_MODE_REUSE).new(current) # save the actual assert instance on the current thread context GdUnitThreadManager.get_current_context().set_assert(self) - if not _base.__validate_value_type(current, TYPE_FLOAT): + if not GdUnitAssertions.validate_value_type(current, TYPE_FLOAT): report_error("GdUnitFloatAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current)) @@ -18,8 +19,8 @@ func _notification(event): _base = null -func __current(): - return _base.__current() +func current_value(): + return _base.current_value() func report_success() -> GdUnitFloatAssert: @@ -32,7 +33,7 @@ func report_error(error :String) -> GdUnitFloatAssert: return self -func _failure_message() -> String: +func failure_message() -> String: return _base._current_error_message @@ -67,77 +68,77 @@ func is_equal_approx(expected :float, approx :float) -> GdUnitFloatAssert: func is_less(expected :float) -> GdUnitFloatAssert: - var current = __current() + var current = current_value() if current == null or current >= expected: return report_error(GdAssertMessages.error_is_value(Comparator.LESS_THAN, current, expected)) return report_success() func is_less_equal(expected :float) -> GdUnitFloatAssert: - var current = __current() + var current = current_value() if current == null or current > expected: return report_error(GdAssertMessages.error_is_value(Comparator.LESS_EQUAL, current, expected)) return report_success() func is_greater(expected :float) -> GdUnitFloatAssert: - var current = __current() + var current = current_value() if current == null or current <= expected: return report_error(GdAssertMessages.error_is_value(Comparator.GREATER_THAN, current, expected)) return report_success() func is_greater_equal(expected :float) -> GdUnitFloatAssert: - var current = __current() + var current = current_value() if current == null or current < expected: return report_error(GdAssertMessages.error_is_value(Comparator.GREATER_EQUAL, current, expected)) return report_success() func is_negative() -> GdUnitFloatAssert: - var current = __current() + var current = current_value() if current == null or current >= 0.0: return report_error(GdAssertMessages.error_is_negative(current)) return report_success() func is_not_negative() -> GdUnitFloatAssert: - var current = __current() + var current = current_value() if current == null or current < 0.0: return report_error(GdAssertMessages.error_is_not_negative(current)) return report_success() func is_zero() -> GdUnitFloatAssert: - var current = __current() + var current = current_value() if current == null or not is_equal_approx(0.00000000, current): return report_error(GdAssertMessages.error_is_zero(current)) return report_success() func is_not_zero() -> GdUnitFloatAssert: - var current = __current() + var current = current_value() if current == null or is_equal_approx(0.00000000, current): return report_error(GdAssertMessages.error_is_not_zero()) return report_success() func is_in(expected :Array) -> GdUnitFloatAssert: - var current = __current() + var current = current_value() if not expected.has(current): return report_error(GdAssertMessages.error_is_in(current, expected)) return report_success() func is_not_in(expected :Array) -> GdUnitFloatAssert: - var current = __current() + var current = current_value() if expected.has(current): return report_error(GdAssertMessages.error_is_not_in(current, expected)) return report_success() func is_between(from :float, to :float) -> GdUnitFloatAssert: - var current = __current() + var current = current_value() if current == null or current < from or current > to: return report_error(GdAssertMessages.error_is_value(Comparator.BETWEEN_EQUAL, current, from, to)) return report_success() diff --git a/addons/gdUnit4/src/asserts/GdUnitFuncAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitFuncAssertImpl.gd index 0c9e4b9f..4902ba05 100644 --- a/addons/gdUnit4/src/asserts/GdUnitFuncAssertImpl.gd +++ b/addons/gdUnit4/src/asserts/GdUnitFuncAssertImpl.gd @@ -15,7 +15,7 @@ var _sleep_timer :Timer = null func _init(instance :Object, func_name :String, args := Array()): - _line_number = GdUnitAssert._get_line_number() + _line_number = GdUnitAssertions.get_line_number() GdAssertReports.reset_last_error_line_number() # save the actual assert instance on the current thread context GdUnitThreadManager.get_current_context().set_assert(self) @@ -49,7 +49,7 @@ func report_error(error_message :String) -> GdUnitFuncAssert: return self -func _failure_message() -> String: +func failure_message() -> String: return _current_error_message @@ -72,43 +72,43 @@ func wait_until(timeout := 2000) -> GdUnitFuncAssert: func is_null() -> GdUnitFuncAssert: - await _validate_callback(__is_null) + await _validate_callback(cb_is_null) return self func is_not_null() -> GdUnitFuncAssert: - await _validate_callback(__is_not_null) + await _validate_callback(cb_is_not_null) return self func is_false() -> GdUnitFuncAssert: - await _validate_callback(__is_false) + await _validate_callback(cb_is_false) return self func is_true() -> GdUnitFuncAssert: - await _validate_callback(__is_true) + await _validate_callback(cb_is_true) return self func is_equal(expected) -> GdUnitFuncAssert: - await _validate_callback(__is_equal, expected) + await _validate_callback(cb_is_equal, expected) return self func is_not_equal(expected) -> GdUnitFuncAssert: - await _validate_callback(__is_not_equal, expected) + await _validate_callback(cb_is_not_equal, expected) return self # we need actually to define this Callable as functions otherwise we results into leaked scripts here # this is actually a Godot bug and needs this kind of workaround -func __is_null(c, _e): return c == null -func __is_not_null(c, _e): return c != null -func __is_false(c, _e): return c == false -func __is_true(c, _e): return c == true -func __is_equal(c, e): return GdObjects.equals(c,e) -func __is_not_equal(c, e): return not GdObjects.equals(c, e) +func cb_is_null(c, _e): return c == null +func cb_is_not_null(c, _e): return c != null +func cb_is_false(c, _e): return c == false +func cb_is_true(c, _e): return c == true +func cb_is_equal(c, e): return GdObjects.equals(c,e) +func cb_is_not_equal(c, e): return not GdObjects.equals(c, e) func _validate_callback(predicate :Callable, expected = null): @@ -128,7 +128,7 @@ func _validate_callback(predicate :Callable, expected = null): _sleep_timer = Timer.new() _sleep_timer.set_name("gdunit_funcassert_sleep_timer_%d" % _sleep_timer.get_instance_id() ) Engine.get_main_loop().root.add_child(_sleep_timer) - + while true: var current = await next_current_value() # is interupted or predicate success @@ -137,14 +137,14 @@ func _validate_callback(predicate :Callable, expected = null): if is_instance_valid(_sleep_timer): _sleep_timer.start(0.05) await _sleep_timer.timeout - + _sleep_timer.stop() await Engine.get_main_loop().process_frame if _interrupted: # https://github.com/godotengine/godot/issues/73052 #var predicate_name = predicate.get_method() var predicate_name :String = str(predicate).split('::')[1] - report_error(GdAssertMessages.error_interrupted(predicate_name.strip_edges().trim_prefix("__"), expected, LocalTime.elapsed(_timeout))) + report_error(GdAssertMessages.error_interrupted(predicate_name.strip_edges().trim_prefix("cb_"), expected, LocalTime.elapsed(_timeout))) else: report_success() _sleep_timer.free() diff --git a/addons/gdUnit4/src/asserts/GdUnitGodotErrorAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitGodotErrorAssertImpl.gd index 169994be..dcd6a9b1 100644 --- a/addons/gdUnit4/src/asserts/GdUnitGodotErrorAssertImpl.gd +++ b/addons/gdUnit4/src/asserts/GdUnitGodotErrorAssertImpl.gd @@ -6,7 +6,8 @@ var _callable :Callable func _init(callable :Callable): # we only support Godot 4.1.x+ because of await issue https://github.com/godotengine/godot/issues/80292 - assert(Engine.get_version_info().hex >= 0x40100, "This assertion is not supported for Godot 4.0.x. Please upgrade to the minimum version Godot 4.1.0!") + assert(Engine.get_version_info().hex >= 0x40100, + "This assertion is not supported for Godot 4.0.x. Please upgrade to the minimum version Godot 4.1.0!") # save the actual assert instance on the current thread context GdUnitThreadManager.get_current_context().set_assert(self) GdAssertReports.reset_last_error_line_number() @@ -15,17 +16,18 @@ func _init(callable :Callable): func _execute() -> Array[ErrorLogEntry]: # execute the given code and monitor for runtime errors - var monitor := GodotGdErrorMonitor.new(true) - monitor.start() if _callable == null or not _callable.is_valid(): _report_error("Invalid Callable '%s'" % _callable) else: await _callable.call() - monitor.stop() - return await monitor.scan() + return await _error_monitor().scan(true) -func _failure_message() -> String: +func _error_monitor() -> GodotGdErrorMonitor: + return GdUnitThreadManager.get_current_context().get_execution_context().error_monitor + + +func failure_message() -> String: return _current_error_message @@ -35,7 +37,7 @@ func _report_success() -> GdUnitAssert: func _report_error(error_message :String, failure_line_number: int = -1) -> GdUnitAssert: - var line_number := failure_line_number if failure_line_number != -1 else GdUnitAssert._get_line_number() + var line_number := failure_line_number if failure_line_number != -1 else GdUnitAssertions.get_line_number() _current_error_message = error_message GdAssertReports.report_error(error_message, line_number) return self @@ -44,6 +46,8 @@ func _report_error(error_message :String, failure_line_number: int = -1) -> GdUn func _has_log_entry(log_entries :Array[ErrorLogEntry], type :ErrorLogEntry.TYPE, error :String) -> bool: for entry in log_entries: if entry._type == type and entry._message == error: + # Erase the log entry we already handled it by this assertion, otherwise it will report at twice + _error_monitor().erase_log_entry(entry) return true return false diff --git a/addons/gdUnit4/src/asserts/GdUnitIntAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitIntAssertImpl.gd index 7f39c060..6fc16e12 100644 --- a/addons/gdUnit4/src/asserts/GdUnitIntAssertImpl.gd +++ b/addons/gdUnit4/src/asserts/GdUnitIntAssertImpl.gd @@ -4,10 +4,11 @@ var _base: GdUnitAssert func _init(current): - _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE).new(current) + _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", + ResourceLoader.CACHE_MODE_REUSE).new(current) # save the actual assert instance on the current thread context GdUnitThreadManager.get_current_context().set_assert(self) - if not _base.__validate_value_type(current, TYPE_INT): + if not GdUnitAssertions.validate_value_type(current, TYPE_INT): report_error("GdUnitIntAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current)) @@ -18,8 +19,8 @@ func _notification(event): _base = null -func __current() -> Variant: - return _base.__current() +func current_value() -> Variant: + return _base.current_value() func report_success() -> GdUnitIntAssert: @@ -32,7 +33,7 @@ func report_error(error :String) -> GdUnitIntAssert: return self -func _failure_message() -> String: +func failure_message() -> String: return _base._current_error_message @@ -62,91 +63,91 @@ func is_not_equal(expected :int) -> GdUnitIntAssert: func is_less(expected :int) -> GdUnitIntAssert: - var current = __current() + var current = current_value() if current == null or current >= expected: return report_error(GdAssertMessages.error_is_value(Comparator.LESS_THAN, current, expected)) return report_success() func is_less_equal(expected :int) -> GdUnitIntAssert: - var current = __current() + var current = current_value() if current == null or current > expected: return report_error(GdAssertMessages.error_is_value(Comparator.LESS_EQUAL, current, expected)) return report_success() func is_greater(expected :int) -> GdUnitIntAssert: - var current = __current() + var current = current_value() if current == null or current <= expected: return report_error(GdAssertMessages.error_is_value(Comparator.GREATER_THAN, current, expected)) return report_success() func is_greater_equal(expected :int) -> GdUnitIntAssert: - var current = __current() + var current = current_value() if current == null or current < expected: return report_error(GdAssertMessages.error_is_value(Comparator.GREATER_EQUAL, current, expected)) return report_success() func is_even() -> GdUnitIntAssert: - var current = __current() + var current = current_value() if current == null or current % 2 != 0: return report_error(GdAssertMessages.error_is_even(current)) return report_success() func is_odd() -> GdUnitIntAssert: - var current = __current() + var current = current_value() if current == null or current % 2 == 0: return report_error(GdAssertMessages.error_is_odd(current)) return report_success() func is_negative() -> GdUnitIntAssert: - var current = __current() + var current = current_value() if current == null or current >= 0: return report_error(GdAssertMessages.error_is_negative(current)) return report_success() func is_not_negative() -> GdUnitIntAssert: - var current = __current() + var current = current_value() if current == null or current < 0: return report_error(GdAssertMessages.error_is_not_negative(current)) return report_success() func is_zero() -> GdUnitIntAssert: - var current = __current() + var current = current_value() if current != 0: return report_error(GdAssertMessages.error_is_zero(current)) return report_success() func is_not_zero() -> GdUnitIntAssert: - var current = __current() + var current = current_value() if current == 0: return report_error(GdAssertMessages.error_is_not_zero()) return report_success() func is_in(expected :Array) -> GdUnitIntAssert: - var current = __current() + var current = current_value() if not expected.has(current): return report_error(GdAssertMessages.error_is_in(current, expected)) return report_success() func is_not_in(expected :Array) -> GdUnitIntAssert: - var current = __current() + var current = current_value() if expected.has(current): return report_error(GdAssertMessages.error_is_not_in(current, expected)) return report_success() func is_between(from :int, to :int) -> GdUnitIntAssert: - var current = __current() + var current = current_value() if current == null or current < from or current > to: return report_error(GdAssertMessages.error_is_value(Comparator.BETWEEN_EQUAL, current, from, to)) return report_success() diff --git a/addons/gdUnit4/src/asserts/GdUnitObjectAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitObjectAssertImpl.gd index 18c67bf7..e3ee8790 100644 --- a/addons/gdUnit4/src/asserts/GdUnitObjectAssertImpl.gd +++ b/addons/gdUnit4/src/asserts/GdUnitObjectAssertImpl.gd @@ -4,14 +4,15 @@ var _base :GdUnitAssert func _init(current): - _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE).new(current) + _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", + ResourceLoader.CACHE_MODE_REUSE).new(current) # save the actual assert instance on the current thread context GdUnitThreadManager.get_current_context().set_assert(self) if (current != null - and (_base.__validate_value_type(current, TYPE_BOOL) - or _base.__validate_value_type(current, TYPE_INT) - or _base.__validate_value_type(current, TYPE_FLOAT) - or _base.__validate_value_type(current, TYPE_STRING))): + and (GdUnitAssertions.validate_value_type(current, TYPE_BOOL) + or GdUnitAssertions.validate_value_type(current, TYPE_INT) + or GdUnitAssertions.validate_value_type(current, TYPE_FLOAT) + or GdUnitAssertions.validate_value_type(current, TYPE_STRING))): report_error("GdUnitObjectAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current)) @@ -22,8 +23,8 @@ func _notification(event): _base = null -func __current() -> Variant: - return _base.__current() +func current_value() -> Variant: + return _base.current_value() func report_success() -> GdUnitObjectAssert: @@ -36,7 +37,7 @@ func report_error(error :String) -> GdUnitObjectAssert: return self -func _failure_message() -> String: +func failure_message() -> String: return _base._current_error_message @@ -67,7 +68,7 @@ func is_not_null() -> GdUnitObjectAssert: @warning_ignore("shadowed_global_identifier") func is_same(expected) -> GdUnitObjectAssert: - var current :Variant = __current() + var current :Variant = current_value() if not is_same(current, expected): report_error(GdAssertMessages.error_is_same(current, expected)) return self @@ -76,7 +77,7 @@ func is_same(expected) -> GdUnitObjectAssert: func is_not_same(expected) -> GdUnitObjectAssert: - var current = __current() + var current = current_value() if is_same(current, expected): report_error(GdAssertMessages.error_not_same(current, expected)) return self @@ -85,7 +86,7 @@ func is_not_same(expected) -> GdUnitObjectAssert: func is_instanceof(type :Object) -> GdUnitObjectAssert: - var current :Object = __current() + var current :Object = current_value() if not is_instance_of(current, type): var result_expected: = GdObjects.extract_class_name(type) var result_current: = GdObjects.extract_class_name(current) @@ -96,7 +97,7 @@ func is_instanceof(type :Object) -> GdUnitObjectAssert: func is_not_instanceof(type) -> GdUnitObjectAssert: - var current :Variant = __current() + var current :Variant = current_value() if is_instance_of(current, type): var result: = GdObjects.extract_class_name(type) if result.is_success(): diff --git a/addons/gdUnit4/src/asserts/GdUnitResultAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitResultAssertImpl.gd index ccc92e56..9598b3eb 100644 --- a/addons/gdUnit4/src/asserts/GdUnitResultAssertImpl.gd +++ b/addons/gdUnit4/src/asserts/GdUnitResultAssertImpl.gd @@ -4,10 +4,11 @@ var _base :GdUnitAssert func _init(current): - _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE).new(current) + _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", + ResourceLoader.CACHE_MODE_REUSE).new(current) # save the actual assert instance on the current thread context GdUnitThreadManager.get_current_context().set_assert(self) - if not __validate_value_type(current): + if not validate_value_type(current): report_error("GdUnitResultAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current)) @@ -18,12 +19,12 @@ func _notification(event): _base = null -func __validate_value_type(value) -> bool: +func validate_value_type(value) -> bool: return value == null or value is GdUnitResult -func __current() -> GdUnitResult: - return _base.__current() as GdUnitResult +func current_value() -> GdUnitResult: + return _base.current_value() as GdUnitResult func report_success() -> GdUnitResultAssert: @@ -36,7 +37,7 @@ func report_error(error :String) -> GdUnitResultAssert: return self -func _failure_message() -> String: +func failure_message() -> String: return _base._current_error_message @@ -55,7 +56,7 @@ func is_not_null() -> GdUnitResultAssert: func is_empty() -> GdUnitResultAssert: - var result := __current() + var result := current_value() if result == null or not result.is_empty(): report_error(GdAssertMessages.error_result_is_empty(result)) else: @@ -64,7 +65,7 @@ func is_empty() -> GdUnitResultAssert: func is_success() -> GdUnitResultAssert: - var result := __current() + var result := current_value() if result == null or not result.is_success(): report_error(GdAssertMessages.error_result_is_success(result)) else: @@ -73,7 +74,7 @@ func is_success() -> GdUnitResultAssert: func is_warning() -> GdUnitResultAssert: - var result := __current() + var result := current_value() if result == null or not result.is_warn(): report_error(GdAssertMessages.error_result_is_warning(result)) else: @@ -82,7 +83,7 @@ func is_warning() -> GdUnitResultAssert: func is_error() -> GdUnitResultAssert: - var result := __current() + var result := current_value() if result == null or not result.is_error(): report_error(GdAssertMessages.error_result_is_error(result)) else: @@ -91,7 +92,7 @@ func is_error() -> GdUnitResultAssert: func contains_message(expected :String) -> GdUnitResultAssert: - var result := __current() + var result := current_value() if result == null: report_error(GdAssertMessages.error_result_has_message("", expected)) return self @@ -107,7 +108,7 @@ func contains_message(expected :String) -> GdUnitResultAssert: func is_value(expected) -> GdUnitResultAssert: - var result := __current() + var result := current_value() var value = null if result == null else result.value() if not GdObjects.equals(value, expected): report_error(GdAssertMessages.error_result_is_value(value, expected)) diff --git a/addons/gdUnit4/src/asserts/GdUnitSignalAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitSignalAssertImpl.gd index b82ebe46..4ef2eeb0 100644 --- a/addons/gdUnit4/src/asserts/GdUnitSignalAssertImpl.gd +++ b/addons/gdUnit4/src/asserts/GdUnitSignalAssertImpl.gd @@ -16,7 +16,7 @@ func _init(emitter :Object): var context := GdUnitThreadManager.get_current_context() context.set_assert(self) _signal_collector = context.get_signal_collector() - _line_number = GdUnitAssert._get_line_number() + _line_number = GdUnitAssertions.get_line_number() _emitter = emitter GdAssertReports.reset_last_error_line_number() @@ -27,7 +27,7 @@ func report_success() -> GdUnitAssert: func report_warning(message :String) -> GdUnitAssert: - GdAssertReports.report_warning(message, GdUnitAssert._get_line_number()) + GdAssertReports.report_warning(message, GdUnitAssertions.get_line_number()) return self @@ -37,7 +37,7 @@ func report_error(error_message :String) -> GdUnitAssert: return self -func _failure_message() -> String: +func failure_message() -> String: return _current_error_message @@ -68,13 +68,13 @@ func is_signal_exists(signal_name :String) -> GdUnitSignalAssert: # Verifies that given signal is emitted until waiting time func is_emitted(name :String, args := []) -> GdUnitSignalAssert: - _line_number = GdUnitAssert._get_line_number() + _line_number = GdUnitAssertions.get_line_number() return await _wail_until_signal(name, args, false) # Verifies that given signal is NOT emitted until waiting time func is_not_emitted(name :String, args := []) -> GdUnitSignalAssert: - _line_number = GdUnitAssert._get_line_number() + _line_number = GdUnitAssertions.get_line_number() return await _wail_until_signal(name, args, true) @@ -101,10 +101,10 @@ func _wail_until_signal(signal_name :String, expected_args :Array, expect_not_em is_signal_emitted = _signal_collector.match(_emitter, signal_name, expected_args) if is_signal_emitted and expect_not_emitted: report_error(GdAssertMessages.error_signal_emitted(signal_name, expected_args, LocalTime.elapsed(int(_timeout-timer.time_left*1000)))) - + if _interrupted and not expect_not_emitted: report_error(GdAssertMessages.error_wait_signal(signal_name, expected_args, LocalTime.elapsed(_timeout))) timer.free() if is_instance_valid(_emitter): - _signal_collector.reset_received_signals(_emitter) + _signal_collector.reset_received_signals(_emitter, signal_name, expected_args) return self diff --git a/addons/gdUnit4/src/asserts/GdUnitStringAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitStringAssertImpl.gd index 154b9e5c..8c1814fd 100644 --- a/addons/gdUnit4/src/asserts/GdUnitStringAssertImpl.gd +++ b/addons/gdUnit4/src/asserts/GdUnitStringAssertImpl.gd @@ -4,10 +4,11 @@ var _base :GdUnitAssert func _init(current): - _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE).new(current) + _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", + ResourceLoader.CACHE_MODE_REUSE).new(current) # save the actual assert instance on the current thread context GdUnitThreadManager.get_current_context().set_assert(self) - if not _base.__validate_value_type(current, TYPE_STRING): + if current != null and typeof(current) != TYPE_STRING and typeof(current) != TYPE_STRING_NAME: report_error("GdUnitStringAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current)) @@ -18,12 +19,12 @@ func _notification(event): _base = null -func _failure_message() -> String: +func failure_message() -> String: return _base._current_error_message -func __current(): - var current = _base.__current() +func current_value(): + var current = _base.current_value() if current == null: return null return current as String @@ -55,92 +56,92 @@ func is_not_null() -> GdUnitStringAssert: func is_equal(expected) -> GdUnitStringAssert: - var current = __current() + var current = current_value() if current == null: return report_error(GdAssertMessages.error_equal(current, expected)) if not GdObjects.equals(current, expected): var diffs := GdDiffTool.string_diff(current, expected) - var formatted_current := GdAssertMessages._colored_array_div(diffs[1]) + var formatted_current := GdAssertMessages.colored_array_div(diffs[1]) return report_error(GdAssertMessages.error_equal(formatted_current, expected)) return report_success() func is_equal_ignoring_case(expected) -> GdUnitStringAssert: - var current = __current() + var current = current_value() if current == null: return report_error(GdAssertMessages.error_equal_ignoring_case(current, expected)) if not GdObjects.equals(current, expected, true): var diffs := GdDiffTool.string_diff(current, expected) - var formatted_current := GdAssertMessages._colored_array_div(diffs[1]) + var formatted_current := GdAssertMessages.colored_array_div(diffs[1]) return report_error(GdAssertMessages.error_equal_ignoring_case(formatted_current, expected)) return report_success() func is_not_equal(expected) -> GdUnitStringAssert: - var current = __current() + var current = current_value() if GdObjects.equals(current, expected): return report_error(GdAssertMessages.error_not_equal(current, expected)) return report_success() func is_not_equal_ignoring_case(expected) -> GdUnitStringAssert: - var current = __current() + var current = current_value() if GdObjects.equals(current, expected, true): return report_error(GdAssertMessages.error_not_equal(current, expected)) return report_success() func is_empty() -> GdUnitStringAssert: - var current = __current() + var current = current_value() if current == null or not current.is_empty(): return report_error(GdAssertMessages.error_is_empty(current)) return report_success() func is_not_empty() -> GdUnitStringAssert: - var current = __current() + var current = current_value() if current == null or current.is_empty(): return report_error(GdAssertMessages.error_is_not_empty()) return report_success() func contains(expected :String) -> GdUnitStringAssert: - var current = __current() + var current = current_value() if current == null or current.find(expected) == -1: return report_error(GdAssertMessages.error_contains(current, expected)) return report_success() func not_contains(expected :String) -> GdUnitStringAssert: - var current = __current() + var current = current_value() if current != null and current.find(expected) != -1: return report_error(GdAssertMessages.error_not_contains(current, expected)) return report_success() func contains_ignoring_case(expected :String) -> GdUnitStringAssert: - var current = __current() + var current = current_value() if current == null or current.findn(expected) == -1: return report_error(GdAssertMessages.error_contains_ignoring_case(current, expected)) return report_success() func not_contains_ignoring_case(expected :String) -> GdUnitStringAssert: - var current = __current() + var current = current_value() if current != null and current.findn(expected) != -1: return report_error(GdAssertMessages.error_not_contains_ignoring_case(current, expected)) return report_success() func starts_with(expected :String) -> GdUnitStringAssert: - var current = __current() + var current = current_value() if current == null or current.find(expected) != 0: return report_error(GdAssertMessages.error_starts_with(current, expected)) return report_success() func ends_with(expected :String) -> GdUnitStringAssert: - var current = __current() + var current = current_value() if current == null: return report_error(GdAssertMessages.error_ends_with(current, expected)) var find = current.length() - expected.length() @@ -149,8 +150,9 @@ func ends_with(expected :String) -> GdUnitStringAssert: return report_success() +# gdlint:disable=max-returns func has_length(expected :int, comparator :int = Comparator.EQUAL) -> GdUnitStringAssert: - var current = __current() + var current = current_value() if current == null: return report_error(GdAssertMessages.error_has_length(current, expected, comparator)) match comparator: diff --git a/addons/gdUnit4/src/asserts/GdUnitVectorAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitVectorAssertImpl.gd index 43a63f1d..9fbe25cb 100644 --- a/addons/gdUnit4/src/asserts/GdUnitVectorAssertImpl.gd +++ b/addons/gdUnit4/src/asserts/GdUnitVectorAssertImpl.gd @@ -5,7 +5,8 @@ var _current_type :int func _init(current :Variant): - _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE).new(current) + _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", + ResourceLoader.CACHE_MODE_REUSE).new(current) # save the actual assert instance on the current thread context GdUnitThreadManager.get_current_context().set_assert(self) if not _validate_value_type(current): @@ -21,14 +22,17 @@ func _notification(event): func _validate_value_type(value) -> bool: - return (value == null + return ( + value == null or typeof(value) in [ TYPE_VECTOR2, TYPE_VECTOR2I, TYPE_VECTOR3, TYPE_VECTOR3I, TYPE_VECTOR4, - TYPE_VECTOR4I]) + TYPE_VECTOR4I + ] + ) func _validate_is_vector_type(value :Variant) -> bool: @@ -39,8 +43,8 @@ func _validate_is_vector_type(value :Variant) -> bool: return false -func __current() -> Variant: - return _base.__current() +func current_value() -> Variant: + return _base.current_value() func report_success() -> GdUnitVectorAssert: @@ -53,7 +57,7 @@ func report_error(error :String) -> GdUnitVectorAssert: return self -func _failure_message() -> String: +func failure_message() -> String: return _base._current_error_message @@ -90,7 +94,7 @@ func is_not_equal(expected :Variant) -> GdUnitVectorAssert: func is_equal_approx(expected :Variant, approx :Variant) -> GdUnitVectorAssert: if not _validate_is_vector_type(expected) or not _validate_is_vector_type(approx): return self - var current = __current() + var current = current_value() var from = expected - approx var to = expected + approx if current == null or (not _is_equal_approx(current, from, to)): @@ -117,7 +121,7 @@ func _is_equal_approx(current, from, to) -> bool: func is_less(expected :Variant) -> GdUnitVectorAssert: if not _validate_is_vector_type(expected): return self - var current = __current() + var current = current_value() if current == null or current >= expected: return report_error(GdAssertMessages.error_is_value(Comparator.LESS_THAN, current, expected)) return report_success() @@ -126,7 +130,7 @@ func is_less(expected :Variant) -> GdUnitVectorAssert: func is_less_equal(expected :Variant) -> GdUnitVectorAssert: if not _validate_is_vector_type(expected): return self - var current = __current() + var current = current_value() if current == null or current > expected: return report_error(GdAssertMessages.error_is_value(Comparator.LESS_EQUAL, current, expected)) return report_success() @@ -135,7 +139,7 @@ func is_less_equal(expected :Variant) -> GdUnitVectorAssert: func is_greater(expected :Variant) -> GdUnitVectorAssert: if not _validate_is_vector_type(expected): return self - var current = __current() + var current = current_value() if current == null or current <= expected: return report_error(GdAssertMessages.error_is_value(Comparator.GREATER_THAN, current, expected)) return report_success() @@ -144,7 +148,7 @@ func is_greater(expected :Variant) -> GdUnitVectorAssert: func is_greater_equal(expected :Variant) -> GdUnitVectorAssert: if not _validate_is_vector_type(expected): return self - var current = __current() + var current = current_value() if current == null or current < expected: return report_error(GdAssertMessages.error_is_value(Comparator.GREATER_EQUAL, current, expected)) return report_success() @@ -153,7 +157,7 @@ func is_greater_equal(expected :Variant) -> GdUnitVectorAssert: func is_between(from :Variant, to :Variant) -> GdUnitVectorAssert: if not _validate_is_vector_type(from) or not _validate_is_vector_type(to): return self - var current = __current() + var current = current_value() if current == null or not (current >= from and current <= to): return report_error(GdAssertMessages.error_is_value(Comparator.BETWEEN_EQUAL, current, from, to)) return report_success() @@ -162,7 +166,7 @@ func is_between(from :Variant, to :Variant) -> GdUnitVectorAssert: func is_not_between(from :Variant, to :Variant) -> GdUnitVectorAssert: if not _validate_is_vector_type(from) or not _validate_is_vector_type(to): return self - var current = __current() + var current = current_value() if (current != null and current >= from and current <= to): return report_error(GdAssertMessages.error_is_value(Comparator.NOT_BETWEEN_EQUAL, current, from, to)) return report_success() diff --git a/addons/gdUnit4/src/cmd/CmdCommandHandler.gd b/addons/gdUnit4/src/cmd/CmdCommandHandler.gd index e32557ec..cd4eecd5 100644 --- a/addons/gdUnit4/src/cmd/CmdCommandHandler.gd +++ b/addons/gdUnit4/src/cmd/CmdCommandHandler.gd @@ -81,12 +81,20 @@ func execute(commands :Array) -> GdUnitResult: var cmd_name := cmd.name() if _command_cbs.has(cmd_name): var cb_s :Callable = _command_cbs.get(cmd_name)[CB_SINGLE_ARG] - var cb_m :Callable = _command_cbs.get(cmd_name)[CB_MULTI_ARGS] - if cmd.arguments().is_empty(): + var arguments := cmd.arguments() + if cb_s and arguments.size() == 0: cb_s.call() + elif cb_s: + cb_s.call(arguments[0]) else: - if cmd.arguments().size() == 1: - cb_s.call(cmd.arguments()[CB_SINGLE_ARG]) - else: - cb_m.callv(cmd.arguments()) + var cb_m :Callable = _command_cbs.get(cmd_name)[CB_MULTI_ARGS] + # we need to find the method and determin the arguments to call the right function + for m in cb_m.get_object().get_method_list(): + if m["name"] == cb_m.get_method(): + if m["args"].size() > 1: + cb_m.callv(arguments) + break + else: + cb_m.call(arguments) + break return GdUnitResult.success(true) diff --git a/addons/gdUnit4/src/cmd/CmdConsole.gd b/addons/gdUnit4/src/cmd/CmdConsole.gd index 5a64b154..c01be583 100644 --- a/addons/gdUnit4/src/cmd/CmdConsole.gd +++ b/addons/gdUnit4/src/cmd/CmdConsole.gd @@ -35,6 +35,16 @@ func color(p_color :Color) -> CmdConsole: return self +func save_cursor() -> CmdConsole: + printraw("") + return self + + +func restore_cursor() -> CmdConsole: + printraw("") + return self + + func end_color() -> CmdConsole: printraw("") return self diff --git a/addons/gdUnit4/src/core/GdFunctionDoubler.gd b/addons/gdUnit4/src/core/GdFunctionDoubler.gd index d3df8a9b..1df1638c 100644 --- a/addons/gdUnit4/src/core/GdFunctionDoubler.gd +++ b/addons/gdUnit4/src/core/GdFunctionDoubler.gd @@ -110,10 +110,11 @@ func double(func_descriptor :GdFunctionDescriptor) -> PackedStringArray: # save original constructor arguments if func_name == "_init": var constructor_args := ",".join(GdFunctionDoubler.extract_constructor_args(args)) - var constructor := "func _init(%s):\n super(%s)\n pass\n" % [constructor_args, ", ".join(arg_names)] + var constructor := "func _init(%s) -> void:\n super(%s)\n pass\n" % [constructor_args, ", ".join(arg_names)] return constructor.split("\n") var double_src := "" + double_src += '@warning_ignore("untyped_declaration")\n' if Engine.get_version_info().hex >= 0x40200 else '\n' if func_descriptor.is_engine(): double_src += '@warning_ignore("native_method_override")\n' double_src += '@warning_ignore("shadowed_variable")\n' @@ -148,7 +149,10 @@ static func extract_constructor_args(args :Array) -> PackedStringArray: var a := arg as GdFunctionArgument var arg_name := a._name var default_value = get_default(a) - constructor_args.append(arg_name + "=" + default_value) + if default_value == "null": + constructor_args.append(arg_name + ":Variant=" + default_value) + else: + constructor_args.append(arg_name + ":=" + default_value) return constructor_args diff --git a/addons/gdUnit4/src/core/GdObjects.gd b/addons/gdUnit4/src/core/GdObjects.gd index ee4bf792..94de509d 100644 --- a/addons/gdUnit4/src/core/GdObjects.gd +++ b/addons/gdUnit4/src/core/GdObjects.gd @@ -178,7 +178,7 @@ static func obj2dict(obj :Object, hashed_objects := Dictionary()) -> Dictionary: return {"%s" % clazz_name : dict} -static func equals(obj_a, obj_b, case_sensitive :bool = false, compare_mode :COMPARE_MODE = COMPARE_MODE.PARAMETER_DEEP_TEST) -> bool: +static func equals(obj_a :Variant, obj_b :Variant, case_sensitive :bool = false, compare_mode :COMPARE_MODE = COMPARE_MODE.PARAMETER_DEEP_TEST) -> bool: return _equals(obj_a, obj_b, case_sensitive, compare_mode, [], 0) @@ -190,7 +190,7 @@ static func equals_sorted(obj_a :Array, obj_b :Array, case_sensitive :bool = fal return equals(a, b, case_sensitive, compare_mode) -static func _equals(obj_a, obj_b, case_sensitive :bool, compare_mode :COMPARE_MODE, deep_stack, stack_depth :int ) -> bool: +static func _equals(obj_a :Variant, obj_b :Variant, case_sensitive :bool, compare_mode :COMPARE_MODE, deep_stack :Array, stack_depth :int ) -> bool: var type_a := typeof(obj_a) var type_b := typeof(obj_b) if stack_depth > 32: @@ -198,6 +198,12 @@ static func _equals(obj_a, obj_b, case_sensitive :bool, compare_mode :COMPARE_MO push_error("GdUnit equals has max stack deep reached!") return false + # use argument matcher if requested + if is_instance_valid(obj_a) and obj_a is GdUnitArgumentMatcher: + return (obj_a as GdUnitArgumentMatcher).is_match(obj_b) + if is_instance_valid(obj_b) and obj_b is GdUnitArgumentMatcher: + return (obj_b as GdUnitArgumentMatcher).is_match(obj_a) + stack_depth += 1 # fast fail is different types if not _is_type_equivalent(type_a, type_b): @@ -305,7 +311,7 @@ static func type_as_string(type :int) -> String: return TYPE_AS_STRING_MAPPINGS.get(type, "Variant") -static func typeof_as_string(value) -> String: +static func typeof_as_string(value :Variant) -> String: return TYPE_AS_STRING_MAPPINGS.get(typeof(value), "Unknown type") @@ -314,15 +320,15 @@ static func all_types() -> PackedInt32Array: static func string_as_typeof(type_name :String) -> int: - var type = TYPE_AS_STRING_MAPPINGS.find_key(type_name) + var type :Variant = TYPE_AS_STRING_MAPPINGS.find_key(type_name) return type if type != null else TYPE_VARIANT -static func is_primitive_type(value) -> bool: +static func is_primitive_type(value :Variant) -> bool: return typeof(value) in [TYPE_BOOL, TYPE_STRING, TYPE_STRING_NAME, TYPE_INT, TYPE_FLOAT] -static func _is_type_equivalent(type_a, type_b) -> bool: +static func _is_type_equivalent(type_a :int, type_b :int) -> bool: # don't test for TYPE_STRING_NAME equivalenz if type_a == TYPE_STRING_NAME or type_b == TYPE_STRING_NAME: return true @@ -353,8 +359,7 @@ static func is_type(value :Variant) -> bool: return false -@warning_ignore("shadowed_global_identifier") -static func _is_same(left, right) -> bool: +static func _is_same(left :Variant, right :Variant) -> bool: var left_type := -1 if left == null else typeof(left) var right_type := -1 if right == null else typeof(right) @@ -366,27 +371,27 @@ static func _is_same(left, right) -> bool: return equals(left, right) -static func is_object(value) -> bool: +static func is_object(value :Variant) -> bool: return typeof(value) == TYPE_OBJECT -static func is_script(value) -> bool: +static func is_script(value :Variant) -> bool: return is_object(value) and value is Script static func is_test_suite(script :Script) -> bool: - return is_gd_testsuite(script) or GdUnit4MonoApiLoader.is_test_suite(script.resource_path) + return is_gd_testsuite(script) or GdUnit4CSharpApiLoader.is_test_suite(script.resource_path) -static func is_native_class(value) -> bool: +static func is_native_class(value :Variant) -> bool: return is_object(value) and is_engine_type(value) -static func is_scene(value) -> bool: +static func is_scene(value :Variant) -> bool: return is_object(value) and value is PackedScene -static func is_scene_resource_path(value) -> bool: +static func is_scene_resource_path(value :Variant) -> bool: return value is String and value.ends_with(".tscn") @@ -432,7 +437,7 @@ static func is_instance(value :Variant) -> bool: # only object form type Node and attached filename -static func is_instance_scene(instance) -> bool: +static func is_instance_scene(instance :Variant) -> bool: if instance is Node: var node := instance as Node return node.get_scene_file_path() != null and not node.get_scene_file_path().is_empty() @@ -445,7 +450,7 @@ static func can_be_instantiate(obj :Variant) -> bool: return obj.has_method("new") -static func create_instance(clazz) -> GdUnitResult: +static func create_instance(clazz :Variant) -> GdUnitResult: match typeof(clazz): TYPE_OBJECT: # test is given clazz already an instance @@ -463,7 +468,7 @@ static func create_instance(clazz) -> GdUnitResult: var clazz_path :String = extract_class_path(clazz)[0] if not FileAccess.file_exists(clazz_path): return GdUnitResult.error("Class '%s' not found." % clazz) - var script = load(clazz_path) + var script := load(clazz_path) if script != null: return GdUnitResult.success(script.new()) else: @@ -471,7 +476,7 @@ static func create_instance(clazz) -> GdUnitResult: return GdUnitResult.error("Can't create instance for class '%s'." % clazz) -static func extract_class_path(clazz) -> PackedStringArray: +static func extract_class_path(clazz :Variant) -> PackedStringArray: var clazz_path := PackedStringArray() if clazz is String: clazz_path.append(clazz) @@ -513,7 +518,7 @@ static func extract_class_name_from_class_path(clazz_path :PackedStringArray) -> return clazz_name -static func extract_class_name(clazz) -> GdUnitResult: +static func extract_class_name(clazz :Variant) -> GdUnitResult: if clazz == null: return GdUnitResult.error("Can't extract class name form a null value.") @@ -529,7 +534,7 @@ static func extract_class_name(clazz) -> GdUnitResult: if ClassDB.class_exists(clazz): return GdUnitResult.success(clazz) var source_sript :Script = load(clazz) - var clazz_name = load("res://addons/gdUnit4/src/core/parse/GdScriptParser.gd").new().get_class_name(source_sript) + var clazz_name :String = load("res://addons/gdUnit4/src/core/parse/GdScriptParser.gd").new().get_class_name(source_sript) return GdUnitResult.success(to_pascal_case(clazz_name)) if is_primitive_type(clazz): @@ -537,12 +542,12 @@ static func extract_class_name(clazz) -> GdUnitResult: if is_script(clazz): if clazz.resource_path.is_empty(): - var class_path = extract_class_name_from_class_path(extract_class_path(clazz)) + var class_path := extract_class_name_from_class_path(extract_class_path(clazz)) return GdUnitResult.success(class_path); return extract_class_name(clazz.resource_path) # need to create an instance for a class typ the extract the class name - var instance = clazz.new() + var instance :Variant = clazz.new() if instance == null: return GdUnitResult.error("Can't create a instance for class '%s'" % clazz) var result := extract_class_name(instance) @@ -589,7 +594,7 @@ static func extract_class_functions(clazz_name :String, script_path :PackedStrin # scans all registert script classes for given # if the class is public in the global space than return true otherwise false # public class means the script class is defined by 'class_name ' -static func is_public_script_class(clazz_name) -> bool: +static func is_public_script_class(clazz_name :String) -> bool: var script_classes:Array[Dictionary] = ProjectSettings.get_global_class_list() for class_info in script_classes: if class_info.has("class"): diff --git a/addons/gdUnit4/src/core/GdUnitFileAccess.gd b/addons/gdUnit4/src/core/GdUnitFileAccess.gd new file mode 100644 index 00000000..4f524e8a --- /dev/null +++ b/addons/gdUnit4/src/core/GdUnitFileAccess.gd @@ -0,0 +1,211 @@ +class_name GdUnitFileAccess +extends RefCounted + +const GDUNIT_TEMP := "user://tmp" + + +static func current_dir() -> String: + return ProjectSettings.globalize_path("res://") + + +static func clear_tmp() -> void: + delete_directory(GDUNIT_TEMP) + + +# Creates a new file under +static func create_temp_file(relative_path :String, file_name :String, mode := FileAccess.WRITE) -> FileAccess: + var file_path := create_temp_dir(relative_path) + "/" + file_name + var file := FileAccess.open(file_path, mode) + if file == null: + push_error("Error creating temporary file at: %s, %s" % [file_path, error_string(FileAccess.get_open_error())]) + return file + + +static func temp_dir() -> String: + if not DirAccess.dir_exists_absolute(GDUNIT_TEMP): + DirAccess.make_dir_recursive_absolute(GDUNIT_TEMP) + return GDUNIT_TEMP + + +static func create_temp_dir(folder_name :String) -> String: + var new_folder := temp_dir() + "/" + folder_name + if not DirAccess.dir_exists_absolute(new_folder): + DirAccess.make_dir_recursive_absolute(new_folder) + return new_folder + + +static func copy_file(from_file :String, to_dir :String) -> GdUnitResult: + var dir := DirAccess.open(to_dir) + if dir != null: + var to_file := to_dir + "/" + from_file.get_file() + prints("Copy %s to %s" % [from_file, to_file]) + var error := dir.copy(from_file, to_file) + if error != OK: + return GdUnitResult.error("Can't copy file form '%s' to '%s'. Error: '%s'" % [from_file, to_file, error_string(error)]) + return GdUnitResult.success(to_file) + return GdUnitResult.error("Directory not found: " + to_dir) + + +static func copy_directory(from_dir :String, to_dir :String, recursive :bool = false) -> bool: + if not DirAccess.dir_exists_absolute(from_dir): + push_error("Source directory not found '%s'" % from_dir) + return false + + # check if destination exists + if not DirAccess.dir_exists_absolute(to_dir): + # create it + var err := DirAccess.make_dir_recursive_absolute(to_dir) + if err != OK: + push_error("Can't create directory '%s'. Error: %s" % [to_dir, error_string(err)]) + return false + var source_dir := DirAccess.open(from_dir) + var dest_dir := DirAccess.open(to_dir) + if source_dir != null: + source_dir.list_dir_begin() + var next := "." + + while next != "": + next = source_dir.get_next() + if next == "" or next == "." or next == "..": + continue + var source := source_dir.get_current_dir() + "/" + next + var dest := dest_dir.get_current_dir() + "/" + next + if source_dir.current_is_dir(): + if recursive: + copy_directory(source + "/", dest, recursive) + continue + var err := source_dir.copy(source, dest) + if err != OK: + push_error("Error checked copy file '%s' to '%s'" % [source, dest]) + return false + + return true + else: + push_error("Directory not found: " + from_dir) + return false + + +static func delete_directory(path :String, only_content := false) -> void: + var dir := DirAccess.open(path) + if dir != null: + dir.list_dir_begin() + var file_name := "." + while file_name != "": + file_name = dir.get_next() + if file_name.is_empty() or file_name == "." or file_name == "..": + continue + var next := path + "/" +file_name + if dir.current_is_dir(): + delete_directory(next) + else: + # delete file + var err := dir.remove(next) + if err: + push_error("Delete %s failed: %s" % [next, error_string(err)]) + if not only_content: + var err := dir.remove(path) + if err: + push_error("Delete %s failed: %s" % [path, error_string(err)]) + + +static func delete_path_index_lower_equals_than(path :String, prefix :String, index :int) -> int: + var dir := DirAccess.open(path) + if dir == null: + return 0 + var deleted := 0 + dir.list_dir_begin() + var next := "." + while next != "": + next = dir.get_next() + if next.is_empty() or next == "." or next == "..": + continue + if next.begins_with(prefix): + var current_index := next.split("_")[1].to_int() + if current_index <= index: + deleted += 1 + delete_directory(path + "/" + next) + return deleted + + +# scans given path for sub directories by given prefix and returns the highest index numer +# e.g. +static func find_last_path_index(path :String, prefix :String) -> int: + var dir := DirAccess.open(path) + if dir == null: + return 0 + var last_iteration := 0 + dir.list_dir_begin() + var next := "." + while next != "": + next = dir.get_next() + if next.is_empty() or next == "." or next == "..": + continue + if next.begins_with(prefix): + var iteration := next.split("_")[1].to_int() + if iteration > last_iteration: + last_iteration = iteration + return last_iteration + + +static func scan_dir(path :String) -> PackedStringArray: + var dir := DirAccess.open(path) + if dir == null or not dir.dir_exists(path): + return PackedStringArray() + var content := PackedStringArray() + dir.list_dir_begin() + var next := "." + while next != "": + next = dir.get_next() + if next.is_empty() or next == "." or next == "..": + continue + content.append(next) + return content + + +static func resource_as_array(resource_path :String) -> PackedStringArray: + var file := FileAccess.open(resource_path, FileAccess.READ) + if file == null: + push_error("ERROR: Can't read resource '%s'. %s" % [resource_path, error_string(FileAccess.get_open_error())]) + return PackedStringArray() + var file_content := PackedStringArray() + while not file.eof_reached(): + file_content.append(file.get_line()) + return file_content + + +static func resource_as_string(resource_path :String) -> String: + var file := FileAccess.open(resource_path, FileAccess.READ) + if file == null: + push_error("ERROR: Can't read resource '%s'. %s" % [resource_path, error_string(FileAccess.get_open_error())]) + return "" + return file.get_as_text(true) + + +static func make_qualified_path(path :String) -> String: + if not path.begins_with("res://"): + if path.begins_with("//"): + return path.replace("//", "res://") + if path.begins_with("/"): + return "res:/" + path + return path + + +static func extract_zip(zip_package :String, dest_path :String) -> GdUnitResult: + var zip: ZIPReader = ZIPReader.new() + var err := zip.open(zip_package) + if err != OK: + return GdUnitResult.error("Extracting `%s` failed! Please collect the error log and report this. Error Code: %s" % [zip_package, err]) + var zip_entries: PackedStringArray = zip.get_files() + # Get base path and step over archive folder + var archive_path := zip_entries[0] + zip_entries.remove_at(0) + + for zip_entry in zip_entries: + var new_file_path: String = dest_path + "/" + zip_entry.replace(archive_path, "") + if zip_entry.ends_with("/"): + DirAccess.make_dir_recursive_absolute(new_file_path) + continue + var file: FileAccess = FileAccess.open(new_file_path, FileAccess.WRITE) + file.store_buffer(zip.read_file(zip_entry)) + zip.close() + return GdUnitResult.success(dest_path) diff --git a/addons/gdUnit4/src/core/GdUnitObjectInteractionsTemplate.gd b/addons/gdUnit4/src/core/GdUnitObjectInteractionsTemplate.gd index 5d455abb..212896ef 100644 --- a/addons/gdUnit4/src/core/GdUnitObjectInteractionsTemplate.gd +++ b/addons/gdUnit4/src/core/GdUnitObjectInteractionsTemplate.gd @@ -5,9 +5,10 @@ var __saved_interactions := Dictionary() var __verified_interactions := Array() -func __save_function_interaction(args :Array) -> void: +func __save_function_interaction(args :Array[Variant]) -> void: var matcher := GdUnitArgumentMatchers.to_matcher(args, true) - for key in __saved_interactions.keys(): + for index in __saved_interactions.keys().size(): + var key :Variant = __saved_interactions.keys()[index] if matcher.is_match(key): __saved_interactions[key] += 1 return @@ -23,11 +24,12 @@ func __do_verify_interactions(times :int = 1) -> Object: return self -func __verify_interactions(args :Array): +func __verify_interactions(args :Array[Variant]) -> void: var summary := Dictionary() var total_interactions := 0 var matcher := GdUnitArgumentMatchers.to_matcher(args, true) - for key in __saved_interactions.keys(): + for index in __saved_interactions.keys().size(): + var key :Variant = __saved_interactions.keys()[index] if matcher.is_match(key): var interactions :int = __saved_interactions.get(key, 0) total_interactions += interactions @@ -37,11 +39,11 @@ func __verify_interactions(args :Array): var gd_assert := GdUnitAssertImpl.new("") if total_interactions != __expected_interactions: - var expected_summary = {args : __expected_interactions} + var expected_summary := {args : __expected_interactions} var error_message :String # if no interactions macht collect not verified interactions for failure report if summary.is_empty(): - var current_summary = __verify_no_more_interactions() + var current_summary := __verify_no_more_interactions() error_message = GdAssertMessages.error_validate_interactions(current_summary, expected_summary) else: error_message = GdAssertMessages.error_validate_interactions(summary, expected_summary) @@ -54,21 +56,23 @@ func __verify_interactions(args :Array): func __verify_no_interactions() -> Dictionary: var summary := Dictionary() if not __saved_interactions.is_empty(): - for func_call in __saved_interactions.keys(): + for index in __saved_interactions.keys().size(): + var func_call :Variant = __saved_interactions.keys()[index] summary[func_call] = __saved_interactions[func_call] return summary func __verify_no_more_interactions() -> Dictionary: var summary := Dictionary() - var called_functions :Array = __saved_interactions.keys() + var called_functions :Array[Variant] = __saved_interactions.keys() if called_functions != __verified_interactions: # collect the not verified functions var called_but_not_verified := called_functions.duplicate() - for verified_function in __verified_interactions: - called_but_not_verified.erase(verified_function) + for index in __verified_interactions.size(): + called_but_not_verified.erase(__verified_interactions[index]) - for not_verified in called_but_not_verified: + for index in called_but_not_verified.size(): + var not_verified :Variant = called_but_not_verified[index] summary[not_verified] = __saved_interactions[not_verified] return summary @@ -77,9 +81,10 @@ func __reset_interactions() -> void: __saved_interactions.clear() -func __filter_vargs(arg_values :Array) -> Array: - var filtered := Array() - for arg in arg_values: +func __filter_vargs(arg_values :Array[Variant]) -> Array[Variant]: + var filtered :Array[Variant] = [] + for index in arg_values.size(): + var arg :Variant = arg_values[index] if typeof(arg) == TYPE_STRING and arg == GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE: continue filtered.append(arg) diff --git a/addons/gdUnit4/src/core/GdUnitProperty.gd b/addons/gdUnit4/src/core/GdUnitProperty.gd index 9373d9c7..6e338a3f 100644 --- a/addons/gdUnit4/src/core/GdUnitProperty.gd +++ b/addons/gdUnit4/src/core/GdUnitProperty.gd @@ -1,15 +1,16 @@ class_name GdUnitProperty extends RefCounted + var _name :String var _help :String var _type :int -var _value +var _value :Variant var _value_set :PackedStringArray -var _default +var _default :Variant -func _init(p_name :String, p_type :int, p_value, p_default_value, p_help :="", p_value_set := PackedStringArray()): +func _init(p_name :String, p_type :int, p_value :Variant, p_default_value :Variant, p_help :="", p_value_set := PackedStringArray()) -> void: _name = p_name _type = p_type _value = p_value @@ -26,7 +27,7 @@ func type() -> int: return _type -func value(): +func value() -> Variant: return _value @@ -52,7 +53,7 @@ func set_value(p_value :Variant) -> void: _value = p_value -func default(): +func default() -> Variant: return _default diff --git a/addons/gdUnit4/src/core/GdUnitResult.gd b/addons/gdUnit4/src/core/GdUnitResult.gd index cda3d51a..f2d297f9 100644 --- a/addons/gdUnit4/src/core/GdUnitResult.gd +++ b/addons/gdUnit4/src/core/GdUnitResult.gd @@ -8,7 +8,7 @@ enum { EMPTY } -var _state +var _state :Variant var _warn_message := "" var _error_message := "" var _value :Variant = null @@ -66,7 +66,7 @@ func value() -> Variant: return _value -func or_else(p_value): +func or_else(p_value :Variant) -> Variant: if not is_success(): return p_value return value() diff --git a/addons/gdUnit4/src/core/GdUnitRunner.gd b/addons/gdUnit4/src/core/GdUnitRunner.gd index c383fc3f..686e4f89 100644 --- a/addons/gdUnit4/src/core/GdUnitRunner.gd +++ b/addons/gdUnit4/src/core/GdUnitRunner.gd @@ -17,11 +17,11 @@ const GDUNIT_RUNNER = "GdUnitRunner" var _config := GdUnitRunnerConfig.new() var _test_suites_to_process :Array -var _state = INIT -var _cs_executor +var _state :int = INIT +var _cs_executor :RefCounted -func _init(): +func _init() -> void: # minimize scene window checked debug mode if OS.get_cmdline_args().size() == 1: DisplayServer.window_set_title("GdUnit4 Runner (Debug Mode)") @@ -30,10 +30,10 @@ func _init(): DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_MINIMIZED) # store current runner instance to engine meta data to can be access in as a singleton Engine.set_meta(GDUNIT_RUNNER, self) - _cs_executor = GdUnit4MonoApiLoader.create_executor(self) + _cs_executor = GdUnit4CSharpApiLoader.create_executor(self) -func _ready(): +func _ready() -> void: var config_result := _config.load_config() if config_result.is_error(): push_error(config_result.error_message()) @@ -48,23 +48,23 @@ func _ready(): _state = INIT -func _on_connection_failed(message :String): +func _on_connection_failed(message :String) -> void: prints("_on_connection_failed", message, _test_suites_to_process) _state = STOP -func _notification(what): +func _notification(what :int) -> void: #prints("GdUnitRunner", self, GdObjects.notification_as_string(what)) if what == NOTIFICATION_PREDELETE: Engine.remove_meta(GDUNIT_RUNNER) -func _process(_delta): +func _process(_delta :float) -> void: match _state: INIT: # wait until client is connected to the GdUnitServer if _client.is_client_connected(): - var time = LocalTime.now() + var time := LocalTime.now() prints("Scan for test suites.") _test_suites_to_process = load_test_suits() prints("Scanning of %d test suites took" % _test_suites_to_process.size(), time.elapsed_since()) @@ -113,7 +113,7 @@ func load_test_suits() -> Array: func gdUnitInit() -> void: #enable_manuall_polling() send_message("Scaned %d test suites" % _test_suites_to_process.size()) - var total_count = _collect_test_case_count(_test_suites_to_process) + var total_count := _collect_test_case_count(_test_suites_to_process) _on_gdunit_event(GdUnitInit.new(_test_suites_to_process.size(), total_count)) for test_suite in _test_suites_to_process: send_test_suite(test_suite) @@ -145,20 +145,20 @@ func _do_filter_test_case(test_suite :Node, test_case :Node, included_tests :Pac func _collect_test_case_count(testSuites :Array) -> int: var total :int = 0 for test_suite in testSuites: - total += (test_suite as Node).get_child_count() + total += test_suite.get_child_count() return total # RPC send functions -func send_message(message :String): +func send_message(message :String) -> void: _client.rpc_send(RPCMessage.of(message)) -func send_test_suite(test_suite): +func send_test_suite(test_suite :Node) -> void: _client.rpc_send(RPCGdUnitTestSuite.of(test_suite)) -func _on_gdunit_event(event :GdUnitEvent): +func _on_gdunit_event(event :GdUnitEvent) -> void: _client.rpc_send(RPCGdUnitEvent.of(event)) diff --git a/addons/gdUnit4/src/core/GdUnitRunnerConfig.gd b/addons/gdUnit4/src/core/GdUnitRunnerConfig.gd index f9816cc4..080c18ea 100644 --- a/addons/gdUnit4/src/core/GdUnitRunnerConfig.gd +++ b/addons/gdUnit4/src/core/GdUnitRunnerConfig.gd @@ -72,10 +72,10 @@ func add_test_case(p_resource_path :String, test_name :StringName, test_param_in # '/path/path', res://path/path', 'res://path/path/testsuite.gd' or 'testsuite' # 'res://path/path/testsuite.gd:test_case' or 'testsuite:test_case' func skip_test_suite(value :StringName) -> GdUnitRunnerConfig: - var parts :Array = GdUnitTools.make_qualified_path(value).rsplit(":") + var parts :Array = GdUnitFileAccess.make_qualified_path(value).rsplit(":") if parts[0] == "res": parts.pop_front() - parts[0] = GdUnitTools.make_qualified_path(parts[0]) + parts[0] = GdUnitFileAccess.make_qualified_path(parts[0]) match parts.size(): 1: skipped()[parts[0]] = PackedStringArray() 2: skip_test_case(parts[0], parts[1]) @@ -108,7 +108,7 @@ func save_config(path :String = CONFIG_FILE) -> GdUnitResult: var file := FileAccess.open(path, FileAccess.WRITE) if file == null: var error = FileAccess.get_open_error() - return GdUnitResult.error("Can't write test runner configuration '%s'! %s" % [path, GdUnitTools.error_as_string(error)]) + return GdUnitResult.error("Can't write test runner configuration '%s'! %s" % [path, error_string(error)]) _config[VERSION] = CONFIG_VERSION file.store_string(JSON.stringify(_config)) return GdUnitResult.success(path) @@ -120,7 +120,7 @@ func load_config(path :String = CONFIG_FILE) -> GdUnitResult: var file := FileAccess.open(path, FileAccess.READ) if file == null: var error = FileAccess.get_open_error() - return GdUnitResult.error("Can't load test runner configuration '%s'! ERROR: %s." % [path, GdUnitTools.error_as_string(error)]) + return GdUnitResult.error("Can't load test runner configuration '%s'! ERROR: %s." % [path, error_string(error)]) var content := file.get_as_text() if not content.is_empty() and content[0] == '{': # Parse as json diff --git a/addons/gdUnit4/src/core/GdUnitSceneRunnerImpl.gd b/addons/gdUnit4/src/core/GdUnitSceneRunnerImpl.gd index ed6dffaa..86f31088 100644 --- a/addons/gdUnit4/src/core/GdUnitSceneRunnerImpl.gd +++ b/addons/gdUnit4/src/core/GdUnitSceneRunnerImpl.gd @@ -26,10 +26,12 @@ var _simulate_start_time :LocalTime var _last_input_event :InputEvent = null var _mouse_button_on_press := [] var _key_on_press := [] +var _curent_mouse_position :Vector2 # time factor settings var _time_factor := 1.0 var _saved_iterations_per_second :float +var _scene_auto_free := false func _init(p_scene, p_verbose :bool, p_hide_push_errors = false): @@ -46,7 +48,8 @@ func _init(p_scene, p_verbose :bool, p_hide_push_errors = false): if not p_hide_push_errors: push_error("GdUnitSceneRunner: The given resource: '%s'. is not a scene." % p_scene) return - _current_scene = load(p_scene).instantiate() + _current_scene = load(p_scene).instantiate() + _scene_auto_free = true else: # verify we have a node instance if not p_scene is Node: @@ -77,8 +80,8 @@ func _notification(what): _reset_input_to_default() if is_instance_valid(_current_scene): _scene_tree.root.remove_child(_current_scene) - # don't free already memory managed instances - if not GdUnitMemoryObserver.is_marked_auto_free(_current_scene): + # do only free scenes instanciated by this runner + if _scene_auto_free: _current_scene.free() _scene_tree = null _current_scene = null @@ -148,18 +151,27 @@ func simulate_mouse_move(pos :Vector2) -> GdUnitSceneRunner: return _handle_input_event(event) -func simulate_mouse_move_relative(relative :Vector2, speed :Vector2 = Vector2.ONE) -> GdUnitSceneRunner: - if _last_input_event is InputEventMouse: - var current_pos :Vector2 = _last_input_event.position - var final_pos := current_pos + relative - var delta_milli := speed.x * 0.1 - var t := 0.0 - while not current_pos.is_equal_approx(final_pos): - t += delta_milli * speed.x - simulate_mouse_move(current_pos) - await _scene_tree.create_timer(delta_milli).timeout - current_pos = current_pos.lerp(final_pos, t) - simulate_mouse_move(final_pos) +func simulate_mouse_move_relative(relative: Vector2, time: float = 1.0, trans_type: Tween.TransitionType = Tween.TRANS_LINEAR) -> GdUnitSceneRunner: + var tween := _scene_tree.create_tween() + _curent_mouse_position = get_mouse_position() + var final_position := _curent_mouse_position + relative + tween.tween_property(self, "_curent_mouse_position", final_position, time).set_trans(trans_type) + tween.play() + + while not get_mouse_position().is_equal_approx(final_position): + simulate_mouse_move(_curent_mouse_position) + await _scene_tree.process_frame + return self + + +func simulate_mouse_move_absolute(position: Vector2, time: float = 1.0, trans_type: Tween.TransitionType = Tween.TRANS_LINEAR) -> GdUnitSceneRunner: + var tween := _scene_tree.create_tween() + _curent_mouse_position = get_mouse_position() + tween.tween_property(self, "_curent_mouse_position", position, time).set_trans(trans_type) + tween.play() + + while not get_mouse_position().is_equal_approx(position): + simulate_mouse_move(_curent_mouse_position) await _scene_tree.process_frame return self @@ -325,6 +337,20 @@ func _apply_input_mouse_position(event :InputEvent) -> void: event.position = _last_input_event.position +## just for testing maunally event to action handling +func _handle_actions(event :InputEvent) -> bool: + var is_action_match := false + for action in InputMap.get_actions(): + if InputMap.event_is_action(event, action, true): + is_action_match = true + prints(action, event, event.is_ctrl_pressed()) + if event.is_pressed(): + Input.action_press(action, InputMap.action_get_deadzone(action)) + else: + Input.action_release(action) + return is_action_match + + # for handling read https://docs.godotengine.org/en/stable/tutorials/inputs/inputevent.html?highlight=inputevent#how-does-it-work func _handle_input_event(event :InputEvent): if event is InputEventMouse: @@ -356,6 +382,7 @@ func _reset_input_to_default() -> void: simulate_key_release(key_scancode) _key_on_press.clear() Input.flush_buffered_events() + _last_input_event = null func __print(message :String) -> void: diff --git a/addons/gdUnit4/src/core/GdUnitSettings.gd b/addons/gdUnit4/src/core/GdUnitSettings.gd index d9466a12..f3e91f54 100644 --- a/addons/gdUnit4/src/core/GdUnitSettings.gd +++ b/addons/gdUnit4/src/core/GdUnitSettings.gd @@ -2,6 +2,7 @@ class_name GdUnitSettings extends RefCounted + const MAIN_CATEGORY = "gdunit4" # Common Settings const COMMON_SETTINGS = MAIN_CATEGORY + "/settings" @@ -86,7 +87,7 @@ enum NAMING_CONVENTIONS { } -static func setup(): +static func setup() -> void: create_property_if_need(UPDATE_NOTIFICATION_ENABLED, true, "Enables/Disables the update notification checked startup.") create_property_if_need(SERVER_TIMEOUT, DEFAULT_SERVER_TIMEOUT, "Sets the server connection timeout in minutes.") create_property_if_need(TEST_TIMEOUT, DEFAULT_TEST_TIMEOUT, "Sets the test case runtime timeout in seconds.") @@ -105,6 +106,7 @@ static func setup(): migrate_properties() + static func migrate_properties() -> void: var TEST_ROOT_FOLDER := "gdunit4/settings/test/test_root_folder" if get_property(TEST_ROOT_FOLDER) != null: @@ -232,7 +234,7 @@ static func list_settings(category :String) -> Array[GdUnitProperty]: for property in ProjectSettings.get_property_list(): var property_name :String = property["name"] if property_name.begins_with(category): - var value = ProjectSettings.get_setting(property_name) + var value :Variant = ProjectSettings.get_setting(property_name) var default :Variant = ProjectSettings.property_get_revert(property_name) var help :String = property["hint_string"] var value_set := extract_value_set_from_help(help) @@ -285,27 +287,27 @@ static func validate_lookup_folder(value :String) -> Variant: return null -static func save_property(name :String, value) -> void: +static func save_property(name :String, value :Variant) -> void: ProjectSettings.set_setting(name, value) _save_settings() static func _save_settings() -> void: - var err = ProjectSettings.save() + var err := ProjectSettings.save() if err != OK: push_error("Save GdUnit4 settings failed : %s" % error_string(err)) return static func has_property(name :String) -> bool: - return ProjectSettings.get_property_list().any( func(property): return property["name"] == name) + return ProjectSettings.get_property_list().any(func(property :Dictionary) -> bool: return property["name"] == name) static func get_property(name :String) -> GdUnitProperty: for property in ProjectSettings.get_property_list(): - var property_name = property["name"] + var property_name :String = property["name"] if property_name == name: - var value = ProjectSettings.get_setting(property_name) + var value :Variant = ProjectSettings.get_setting(property_name) var default :Variant = ProjectSettings.property_get_revert(property_name) var help :String = property["hint_string"] var value_set := extract_value_set_from_help(help) @@ -318,7 +320,7 @@ static func migrate_property(old_property :String, new_property :String, default if property == null: prints("Migration not possible, property '%s' not found" % old_property) return - var value = converter.call(property.value()) if converter.is_valid() else property.value() + var value :Variant = converter.call(property.value()) if converter.is_valid() else property.value() ProjectSettings.set_setting(new_property, value) ProjectSettings.set_initial_value(new_property, default_value) set_help(new_property, value, help) diff --git a/addons/gdUnit4/src/core/GdUnitSignalCollector.gd b/addons/gdUnit4/src/core/GdUnitSignalCollector.gd index cf55adcf..f797e15c 100644 --- a/addons/gdUnit4/src/core/GdUnitSignalCollector.gd +++ b/addons/gdUnit4/src/core/GdUnitSignalCollector.gd @@ -63,17 +63,18 @@ func _on_signal_emmited( arg0=NO_ARG, arg1=NO_ARG, arg2=NO_ARG, arg3=NO_ARG, arg # extract the emitter and signal_name from the last two arguments (see line 61 where is added) var signal_name :String = signal_args.pop_back() var emitter :Object = signal_args.pop_back() - # prints("_on_signal_emmited:", emitter, signal_name, signal_args) + #prints("_on_signal_emmited:", emitter, signal_name, signal_args) if is_signal_collecting(emitter, signal_name): _collected_signals[emitter][signal_name].append(signal_args) -func reset_received_signals(emitter :Object): - # _debug_signal_list("before claer"); +func reset_received_signals(emitter :Object, signal_name: String, signal_args :Array): + #_debug_signal_list("before claer"); if _collected_signals.has(emitter): - for signal_name in _collected_signals[emitter]: - _collected_signals[emitter][signal_name].clear() - # _debug_signal_list("after claer"); + var signals_by_emitter = _collected_signals[emitter] + if signals_by_emitter.has(signal_name): + _collected_signals[emitter][signal_name].erase(signal_args) + #_debug_signal_list("after claer"); func is_signal_collecting(emitter :Object, signal_name :String) -> bool: @@ -85,7 +86,7 @@ func match(emitter :Object, signal_name :String, args :Array) -> bool: if _collected_signals.is_empty() or not _collected_signals.has(emitter): return false for received_args in _collected_signals[emitter][signal_name]: - # prints("testing", signal_name, received_args, "vs", args) + #prints("testing", signal_name, received_args, "vs", args) if GdObjects.equals(received_args, args): return true return false diff --git a/addons/gdUnit4/src/core/GdUnitTestSuiteBuilder.gd b/addons/gdUnit4/src/core/GdUnitTestSuiteBuilder.gd index 7cd5dfdd..5e49c41c 100644 --- a/addons/gdUnit4/src/core/GdUnitTestSuiteBuilder.gd +++ b/addons/gdUnit4/src/core/GdUnitTestSuiteBuilder.gd @@ -8,7 +8,7 @@ static func create(source :Script, line_number :int) -> GdUnitResult: ScriptEditorControls.save_an_open_script(source.resource_path) ScriptEditorControls.save_an_open_script(test_suite_path, true) if GdObjects.is_cs_script(source): - return GdUnit4MonoApiLoader.create_test_suite(source.resource_path, line_number+1, test_suite_path) + return GdUnit4CSharpApiLoader.create_test_suite(source.resource_path, line_number+1, test_suite_path) var parser := GdScriptParser.new() var lines := source.source_code.split("\n") var current_line := lines[line_number] diff --git a/addons/gdUnit4/src/core/GdUnitTestSuiteScanner.gd b/addons/gdUnit4/src/core/GdUnitTestSuiteScanner.gd index ff7ab6df..75945587 100644 --- a/addons/gdUnit4/src/core/GdUnitTestSuiteScanner.gd +++ b/addons/gdUnit4/src/core/GdUnitTestSuiteScanner.gd @@ -79,8 +79,8 @@ static func _file(dir :DirAccess, file_name :String) -> String: func _parse_is_test_suite(resource_path :String) -> Node: if not GdUnitTestSuiteScanner._is_script_format_supported(resource_path): return null - if GdUnit4MonoApiLoader.is_test_suite(resource_path): - return GdUnit4MonoApiLoader.parse_test_suite(resource_path) + if GdUnit4CSharpApiLoader.is_test_suite(resource_path): + return GdUnit4CSharpApiLoader.parse_test_suite(resource_path) var script :Script = ResourceLoader.load(resource_path) if not GdObjects.is_test_suite(script): return null @@ -93,7 +93,7 @@ static func _is_script_format_supported(resource_path :String) -> bool: var ext := resource_path.get_extension() if ext == "gd": return true - return GdUnit4MonoApiLoader.is_csharp_file(resource_path) + return GdUnit4CSharpApiLoader.is_csharp_file(resource_path) func _parse_test_suite(script :GDScript) -> GdUnitTestSuite: diff --git a/addons/gdUnit4/src/core/GdUnitTools.gd b/addons/gdUnit4/src/core/GdUnitTools.gd index 1e4f660a..d577c2b0 100644 --- a/addons/gdUnit4/src/core/GdUnitTools.gd +++ b/addons/gdUnit4/src/core/GdUnitTools.gd @@ -1,185 +1,5 @@ extends RefCounted -const GDUNIT_TEMP := "user://tmp" - - -static func temp_dir() -> String: - if not DirAccess.dir_exists_absolute(GDUNIT_TEMP): - DirAccess.make_dir_recursive_absolute(GDUNIT_TEMP) - return GDUNIT_TEMP - - -static func create_temp_dir(folder_name :String) -> String: - var new_folder = temp_dir() + "/" + folder_name - if not DirAccess.dir_exists_absolute(new_folder): - DirAccess.make_dir_recursive_absolute(new_folder) - return new_folder - - -static func clear_tmp(): - delete_directory(GDUNIT_TEMP) - - -# Creates a new file under -static func create_temp_file(relative_path :String, file_name :String, mode := FileAccess.WRITE) -> FileAccess: - var file_path := create_temp_dir(relative_path) + "/" + file_name - var file = FileAccess.open(file_path, mode) - if file == null: - push_error("Error creating temporary file at: %s, %s" % [file_path, error_as_string(FileAccess.get_open_error())]) - return file - - -static func current_dir() -> String: - return ProjectSettings.globalize_path("res://") - - -static func delete_directory(path :String, only_content := false) -> void: - var dir := DirAccess.open(path) - if dir != null: - dir.list_dir_begin() # TODOGODOT4 fill missing arguments https://github.com/godotengine/godot/pull/40547 - var file_name := "." - while file_name != "": - file_name = dir.get_next() - if file_name.is_empty() or file_name == "." or file_name == "..": - continue - var next := path + "/" +file_name - if dir.current_is_dir(): - delete_directory(next) - else: - # delete file - var err = dir.remove(next) - if err: - push_error("Delete %s failed: %s" % [next, error_as_string(err)]) - if not only_content: - var err := dir.remove(path) - if err: - push_error("Delete %s failed: %s" % [path, error_as_string(err)]) - - -static func copy_file(from_file :String, to_dir :String) -> GdUnitResult: - var dir := DirAccess.open(to_dir) - if dir != null: - var to_file := to_dir + "/" + from_file.get_file() - prints("Copy %s to %s" % [from_file, to_file]) - var error = dir.copy(from_file, to_file) - if error != OK: - return GdUnitResult.error("Can't copy file form '%s' to '%s'. Error: '%s'" % [from_file, to_file, error_as_string(error)]) - return GdUnitResult.success(to_file) - return GdUnitResult.error("Directory not found: " + to_dir) - - -static func copy_directory(from_dir :String, to_dir :String, recursive :bool = false) -> bool: - if not DirAccess.dir_exists_absolute(from_dir): - push_error("Source directory not found '%s'" % from_dir) - return false - - # check if destination exists - if not DirAccess.dir_exists_absolute(to_dir): - # create it - var err := DirAccess.make_dir_recursive_absolute(to_dir) - if err != OK: - push_error("Can't create directory '%s'. Error: %s" % [to_dir, error_as_string(err)]) - return false - var source_dir := DirAccess.open(from_dir) - var dest_dir := DirAccess.open(to_dir) - if source_dir != null: - source_dir.list_dir_begin() # TODOGODOT4 fill missing arguments https://github.com/godotengine/godot/pull/40547 - var next := "." - - while next != "": - next = source_dir.get_next() - if next == "" or next == "." or next == "..": - continue - var source := source_dir.get_current_dir() + "/" + next - var dest := dest_dir.get_current_dir() + "/" + next - if source_dir.current_is_dir(): - if recursive: - copy_directory(source + "/", dest, recursive) - continue - var err = source_dir.copy(source, dest) - if err != OK: - push_error("Error checked copy file '%s' to '%s'" % [source, dest]) - return false - - return true - else: - push_error("Directory not found: " + from_dir) - return false - - -# scans given path for sub directories by given prefix and returns the highest index numer -# e.g. -static func find_last_path_index(path :String, prefix :String) -> int: - var dir := DirAccess.open(path) - if dir == null: - return 0 - var last_iteration := 0 - dir.list_dir_begin() # TODOGODOT4 fill missing arguments https://github.com/godotengine/godot/pull/40547 - var next := "." - while next != "": - next = dir.get_next() - if next.is_empty() or next == "." or next == "..": - continue - if next.begins_with(prefix): - var iteration := next.split("_")[1].to_int() - if iteration > last_iteration: - last_iteration = iteration - return last_iteration - - -static func delete_path_index_lower_equals_than(path :String, prefix :String, index :int) -> int: - var dir := DirAccess.open(path) - if dir == null: - return 0 - var deleted := 0 - dir.list_dir_begin() # TODOGODOT4 fill missing arguments https://github.com/godotengine/godot/pull/40547 - var next := "." - while next != "": - next = dir.get_next() - if next.is_empty() or next == "." or next == "..": - continue - if next.begins_with(prefix): - var current_index := next.split("_")[1].to_int() - if current_index <= index: - deleted += 1 - delete_directory(path + "/" + next) - return deleted - - -static func scan_dir(path :String) -> PackedStringArray: - var dir := DirAccess.open(path) - if dir == null or not dir.dir_exists(path): - return PackedStringArray() - var content := PackedStringArray() - dir.list_dir_begin() # TODOGODOT4 fill missing arguments https://github.com/godotengine/godot/pull/40547 - var next := "." - while next != "": - next = dir.get_next() - if next.is_empty() or next == "." or next == "..": - continue - content.append(next) - return content - - -static func resource_as_array(resource_path :String) -> PackedStringArray: - var file := FileAccess.open(resource_path, FileAccess.READ) - if file == null: - push_error("ERROR: Can't read resource '%s'. %s" % [resource_path, error_as_string(FileAccess.get_open_error())]) - return PackedStringArray() - var file_content := PackedStringArray() - while not file.eof_reached(): - file_content.append(file.get_line()) - return file_content - - -static func resource_as_string(resource_path :String) -> String: - var file := FileAccess.open(resource_path, FileAccess.READ) - if file == null: - push_error("ERROR: Can't read resource '%s'. %s" % [resource_path, error_as_string(FileAccess.get_open_error())]) - return "" - return file.get_as_text(true) - - static func normalize_text(text :String) -> String: return text.replace("\r", ""); @@ -221,6 +41,7 @@ static func free_instance(instance :Variant, is_stdout_verbose :=false) -> bool: if instance is RefCounted: instance.notification(Object.NOTIFICATION_PREDELETE) await Engine.get_main_loop().process_frame + await Engine.get_main_loop().physics_frame return true else: # is instance already freed? @@ -241,7 +62,7 @@ static func free_instance(instance :Variant, is_stdout_verbose :=false) -> bool: return !is_instance_valid(instance) -static func _release_connections(instance :Object): +static func _release_connections(instance :Object) -> void: if is_instance_valid(instance): # disconnect from all connected signals to force freeing, otherwise it ends up in orphans for connection in instance.get_incoming_connections(): @@ -256,7 +77,7 @@ static func _release_connections(instance :Object): release_timers() -static func release_timers(): +static func release_timers() -> void: # we go the new way to hold all gdunit timers in group 'GdUnitTimers' for node in Engine.get_main_loop().root.get_children(): if is_instance_valid(node) and node.is_in_group("GdUnitTimers"): @@ -267,7 +88,7 @@ static func release_timers(): # the finally cleaup unfreed resources and singletons -static func dispose_all(): +static func dispose_all() -> void: release_timers() GdUnitSignals.dispose() GdUnitSingleton.dispose() @@ -279,46 +100,12 @@ static func release_double(instance :Object) -> void: instance.call("__release_double") -static func make_qualified_path(path :String) -> String: - if not path.begins_with("res://"): - if path.begins_with("//"): - return path.replace("//", "res://") - if path.begins_with("/"): - return "res:/" + path - return path - - -static func error_as_string(error_number :int) -> String: - return error_string(error_number) - - static func clear_push_errors() -> void: - var runner = Engine.get_meta("GdUnitRunner") + var runner :Node = Engine.get_meta("GdUnitRunner") if runner != null: runner.clear_push_errors() static func register_expect_interupted_by_timeout(test_suite :Node, test_case_name :String) -> void: - var test_case = test_suite.find_child(test_case_name, false, false) + var test_case :Node = test_suite.find_child(test_case_name, false, false) test_case.expect_to_interupt() - - -static func extract_zip(zip_package :String, dest_path :String) -> GdUnitResult: - var zip: ZIPReader = ZIPReader.new() - var err := zip.open(zip_package) - if err != OK: - return GdUnitResult.error("Extracting `%s` failed! Please collect the error log and report this. Error Code: %s" % [zip_package, err]) - var zip_entries: PackedStringArray = zip.get_files() - # Get base path and step over archive folder - var archive_path = zip_entries[0] - zip_entries.remove_at(0) - - for zip_entry in zip_entries: - var new_file_path: String = dest_path + "/" + zip_entry.replace(archive_path, "") - if zip_entry.ends_with("/"): - DirAccess.make_dir_recursive_absolute(new_file_path) - continue - var file: FileAccess = FileAccess.open(new_file_path, FileAccess.WRITE) - file.store_buffer(zip.read_file(zip_entry)) - zip.close() - return GdUnitResult.success(dest_path) diff --git a/addons/gdUnit4/src/core/GodotVersionFixures.gd b/addons/gdUnit4/src/core/GodotVersionFixures.gd index d0ce4e14..2c331d5f 100644 --- a/addons/gdUnit4/src/core/GodotVersionFixures.gd +++ b/addons/gdUnit4/src/core/GodotVersionFixures.gd @@ -9,3 +9,13 @@ static func get_icon(control :Control, icon_name :String) -> Texture2D: if Engine.get_version_info().hex >= 040200: return control.get_theme_icon(icon_name, "EditorIcons") return control.theme.get_icon(icon_name, "EditorIcons") + + +@warning_ignore("shadowed_global_identifier") +static func type_convert(value: Variant, type: int): + return convert(value, type) + + +@warning_ignore("shadowed_global_identifier") +static func convert(value: Variant, type: int) -> Variant: + return type_convert(value, type) diff --git a/addons/gdUnit4/src/core/_TestCase.gd b/addons/gdUnit4/src/core/_TestCase.gd index 47ea60ff..042f3077 100644 --- a/addons/gdUnit4/src/core/_TestCase.gd +++ b/addons/gdUnit4/src/core/_TestCase.gd @@ -26,15 +26,6 @@ var _failed := false var _report :GdUnitReport = null -var monitor : GodotGdErrorMonitor = null: - set (value): - monitor = value - get: - if monitor == null: - monitor = GodotGdErrorMonitor.new() - return monitor - - var timeout : int = DEFAULT_TIMEOUT: set (value): timeout = value @@ -62,31 +53,19 @@ func execute(p_test_parameter := Array(), p_iteration := 0): if _current_iteration == -1: _set_failure_handler() set_timeout() - monitor.start() if not p_test_parameter.is_empty(): update_fuzzers(p_test_parameter, p_iteration) _execute_test_case(name, p_test_parameter) else: _execute_test_case(name, []) await completed - monitor.stop() - for report_ in monitor.reports(): - if report_.is_error(): - _report = report_ - _interupted = true func execute_paramaterized(p_test_parameter :Array): _failure_received(false) set_timeout() - monitor.start() _execute_test_case(name, p_test_parameter) await completed - monitor.stop() - for report_ in monitor.reports(): - if report_.is_error(): - _report = report_ - _interupted = true var _is_disposed := false diff --git a/addons/gdUnit4/src/core/event/GdUnitEvent.gd b/addons/gdUnit4/src/core/event/GdUnitEvent.gd index dbfcc326..cd922088 100644 --- a/addons/gdUnit4/src/core/event/GdUnitEvent.gd +++ b/addons/gdUnit4/src/core/event/GdUnitEvent.gd @@ -26,10 +26,10 @@ var _suite_name :String var _test_name :String var _total_count :int = 0 var _statistics := Dictionary() -var _reports := Array() +var _reports :Array[GdUnitReport] = [] -func suite_before(p_resource_path :String, p_suite_name :String, p_total_count) -> GdUnitEvent: +func suite_before(p_resource_path :String, p_suite_name :String, p_total_count :int) -> GdUnitEvent: _event_type = TESTSUITE_BEFORE _resource_path = p_resource_path _suite_name = p_suite_name @@ -38,7 +38,7 @@ func suite_before(p_resource_path :String, p_suite_name :String, p_total_count) return self -func suite_after(p_resource_path :String, p_suite_name :String, p_statistics :Dictionary = {}, p_reports :Array = []) -> GdUnitEvent: +func suite_after(p_resource_path :String, p_suite_name :String, p_statistics :Dictionary = {}, p_reports :Array[GdUnitReport] = []) -> GdUnitEvent: _event_type = TESTSUITE_AFTER _resource_path = p_resource_path _suite_name = p_suite_name @@ -56,7 +56,7 @@ func test_before(p_resource_path :String, p_suite_name :String, p_test_name :Str return self -func test_after(p_resource_path :String, p_suite_name :String, p_test_name :String, p_statistics :Dictionary = {}, p_reports :Array = []) -> GdUnitEvent: +func test_after(p_resource_path :String, p_suite_name :String, p_test_name :String, p_statistics :Dictionary = {}, p_reports :Array[GdUnitReport] = []) -> GdUnitEvent: _event_type = TESTCASE_AFTER _resource_path = p_resource_path _suite_name = p_suite_name @@ -138,7 +138,7 @@ func reports() -> Array: return _reports -func _to_string(): +func _to_string() -> String: return "Event: %s %s:%s, %s, %s" % [_event_type, _suite_name, _test_name, _statistics, _reports] @@ -161,20 +161,24 @@ func deserialize(serialized :Dictionary) -> GdUnitEvent: _suite_name = serialized.get("suite_name", null) _test_name = serialized.get("test_name", "unknown") _total_count = serialized.get("total_count", 0) - _statistics = serialized.get("statistics", Dictionary()) - _reports = _deserialize_reports(serialized.get("reports",[])) + _statistics = serialized.get("statistics", Dictionary()) + if serialized.has("reports"): + # needs this workaround to copy typed values in the array + var reports :Array[Dictionary] = [] + reports.append_array(serialized.get("reports")) + _reports = _deserialize_reports(reports) return self -func _serialize_TestReports() -> Array: - var serialized_reports := Array() +func _serialize_TestReports() -> Array[Dictionary]: + var serialized_reports :Array[Dictionary] = [] for report in _reports: serialized_reports.append(report.serialize()) return serialized_reports -func _deserialize_reports(p_reports :Array) -> Array: - var deserialized_reports := Array() +func _deserialize_reports(p_reports :Array[Dictionary]) -> Array[GdUnitReport]: + var deserialized_reports :Array[GdUnitReport] = [] for report in p_reports: var test_report := GdUnitReport.new().deserialize(report) deserialized_reports.append(test_report) diff --git a/addons/gdUnit4/src/core/event/GdUnitEventInit.gd b/addons/gdUnit4/src/core/event/GdUnitEventInit.gd index eeb88dce..8bb1d496 100644 --- a/addons/gdUnit4/src/core/event/GdUnitEventInit.gd +++ b/addons/gdUnit4/src/core/event/GdUnitEventInit.gd @@ -4,7 +4,8 @@ extends GdUnitEvent var _total_testsuites :int -func _init(p_total_testsuites :int, p_total_count :int): + +func _init(p_total_testsuites :int, p_total_count :int) -> void: _event_type = INIT _total_testsuites = p_total_testsuites _total_count = p_total_count diff --git a/addons/gdUnit4/src/core/event/GdUnitEventStop.gd b/addons/gdUnit4/src/core/event/GdUnitEventStop.gd index faa6a186..d7a3c11c 100644 --- a/addons/gdUnit4/src/core/event/GdUnitEventStop.gd +++ b/addons/gdUnit4/src/core/event/GdUnitEventStop.gd @@ -2,6 +2,5 @@ class_name GdUnitStop extends GdUnitEvent -func _init(): +func _init() -> void: _event_type = STOP - diff --git a/addons/gdUnit4/src/core/execution/GdUnitExecutionContext.gd b/addons/gdUnit4/src/core/execution/GdUnitExecutionContext.gd index b55c125f..16117615 100644 --- a/addons/gdUnit4/src/core/execution/GdUnitExecutionContext.gd +++ b/addons/gdUnit4/src/core/execution/GdUnitExecutionContext.gd @@ -12,6 +12,15 @@ var _test_case_name: StringName var _name :String +var error_monitor : GodotGdErrorMonitor = null: + set (value): + error_monitor = value + get: + if _parent_context != null: + return _parent_context.error_monitor + return error_monitor + + var test_suite : GdUnitTestSuite = null: set (value): test_suite = value @@ -35,6 +44,7 @@ func _init(name :String, parent_context :GdUnitExecutionContext = null) -> void: _orphan_monitor = GdUnitOrphanNodesMonitor.new(name) _orphan_monitor.start() _memory_observer = GdUnitMemoryObserver.new() + error_monitor = GodotGdErrorMonitor.new() _report_collector = GdUnitTestReportCollector.new(get_instance_id()) if parent_context != null: parent_context._sub_context.append(self) @@ -84,6 +94,17 @@ func test_failed() -> bool: return has_failures() or has_errors() +func error_monitor_start() -> void: + error_monitor.start() + + +func error_monitor_stop() -> void: + await error_monitor.scan() + for error_report in error_monitor.to_reports(): + if error_report.is_error(): + _report_collector._reports.append(error_report) + + func orphan_monitor_start() -> void: _orphan_monitor.start() diff --git a/addons/gdUnit4/src/core/execution/GdUnitTestSuiteExecutor.gd b/addons/gdUnit4/src/core/execution/GdUnitTestSuiteExecutor.gd index 47c02fb8..3392bde0 100644 --- a/addons/gdUnit4/src/core/execution/GdUnitTestSuiteExecutor.gd +++ b/addons/gdUnit4/src/core/execution/GdUnitTestSuiteExecutor.gd @@ -8,12 +8,12 @@ var _assertions := GdUnitAssertions.new() var _executeStage :IGdUnitExecutionStage = GdUnitTestSuiteExecutionStage.new() -func _init(debug_mode :bool = false): +func _init(debug_mode :bool = false) -> void: _executeStage.set_debug_mode(debug_mode) func execute(test_suite :GdUnitTestSuite) -> void: - var orphan_detection_enabled = GdUnitSettings.is_verbose_orphans() + var orphan_detection_enabled := GdUnitSettings.is_verbose_orphans() if not orphan_detection_enabled: prints("!!! Reporting orphan nodes is disabled. Please check GdUnit settings.") diff --git a/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseAfterStage.gd b/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseAfterStage.gd index d8db2c44..d0c8de13 100644 --- a/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseAfterStage.gd +++ b/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseAfterStage.gd @@ -14,14 +14,13 @@ func _init(call_stage := true): func _execute(context :GdUnitExecutionContext) -> void: var test_suite := context.test_suite - if _call_stage: @warning_ignore("redundant_await") await test_suite.after_test() # unreference last used assert form the test to prevent memory leaks GdUnitThreadManager.get_current_context().set_assert(null) await context.gc() - + await context.error_monitor_stop() if context.test_case.is_skipped(): fire_test_skipped(context) else: diff --git a/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseBeforeStage.gd b/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseBeforeStage.gd index 0abc581f..6a1a54d8 100644 --- a/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseBeforeStage.gd +++ b/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseBeforeStage.gd @@ -22,6 +22,7 @@ func _execute(context :GdUnitExecutionContext) -> void: if _call_stage: @warning_ignore("redundant_await") await test_suite.before_test() + context.error_monitor_start() func set_test_name(test_name :StringName): diff --git a/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteAfterStage.gd b/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteAfterStage.gd index dab18da1..5606f39f 100644 --- a/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteAfterStage.gd +++ b/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteAfterStage.gd @@ -23,6 +23,6 @@ func _execute(context :GdUnitExecutionContext) -> void: .create(GdUnitReport.WARN, 1, GdAssertMessages.orphan_detected_on_suite_setup(orphans))) fire_event(GdUnitEvent.new().suite_after(test_suite.get_script().resource_path, test_suite.get_name(), context.build_report_statistics(orphans, false), reports)) - GdUnitTools.clear_tmp() + GdUnitFileAccess.clear_tmp() # Guard that checks if all doubled (spy/mock) objects are released GdUnitClassDoubler.check_leaked_instances() diff --git a/addons/gdUnit4/src/core/parse/GdFunctionArgument.gd b/addons/gdUnit4/src/core/parse/GdFunctionArgument.gd index 08f70079..55de0b69 100644 --- a/addons/gdUnit4/src/core/parse/GdFunctionArgument.gd +++ b/addons/gdUnit4/src/core/parse/GdFunctionArgument.gd @@ -20,7 +20,7 @@ func name() -> String: func default() -> Variant: - return convert(_default_value, _type) + return GodotVersionFixures.convert(_default_value, _type) func value_as_string() -> String: diff --git a/addons/gdUnit4/src/core/report/GdUnitReport.gd b/addons/gdUnit4/src/core/report/GdUnitReport.gd index 90b260a8..eb7ed2e5 100644 --- a/addons/gdUnit4/src/core/report/GdUnitReport.gd +++ b/addons/gdUnit4/src/core/report/GdUnitReport.gd @@ -18,7 +18,7 @@ var _line_number :int var _message :String -func create(p_type, p_line_number :int, p_message :String) -> GdUnitReport: +func create(p_type :int, p_line_number :int, p_message :String) -> GdUnitReport: _type = p_type _line_number = p_line_number _message = p_message @@ -53,7 +53,7 @@ func is_error() -> bool: return _type == TERMINATED or _type == INTERUPTED or _type == ABORT -func _to_string(): +func _to_string() -> String: if _line_number == -1: return "[color=green]line [/color][color=aqua]:[/color] %s" % [_message] return "[color=green]line [/color][color=aqua]%d:[/color] %s" % [_line_number, _message] @@ -67,7 +67,7 @@ func serialize() -> Dictionary: } -func deserialize(serialized:Dictionary) -> GdUnitReport: +func deserialize(serialized :Dictionary) -> GdUnitReport: _type = serialized["type"] _line_number = serialized["line_number"] _message = serialized["message"] diff --git a/addons/gdUnit4/src/matchers/AnyArgumentMatcher.gd b/addons/gdUnit4/src/matchers/AnyArgumentMatcher.gd index 6eca57a6..8544c403 100644 --- a/addons/gdUnit4/src/matchers/AnyArgumentMatcher.gd +++ b/addons/gdUnit4/src/matchers/AnyArgumentMatcher.gd @@ -5,3 +5,7 @@ extends GdUnitArgumentMatcher @warning_ignore("unused_parameter") func is_match(value) -> bool: return true + + +func _to_string() -> String: + return "any()" diff --git a/addons/gdUnit4/src/matchers/AnyBuildInTypeArgumentMatcher.gd b/addons/gdUnit4/src/matchers/AnyBuildInTypeArgumentMatcher.gd index 3c4026a2..04faae92 100644 --- a/addons/gdUnit4/src/matchers/AnyBuildInTypeArgumentMatcher.gd +++ b/addons/gdUnit4/src/matchers/AnyBuildInTypeArgumentMatcher.gd @@ -3,8 +3,48 @@ extends GdUnitArgumentMatcher var _type : PackedInt32Array = [] + func _init(type :PackedInt32Array): _type = type + func is_match(value) -> bool: return _type.has(typeof(value)) + + +func _to_string() -> String: + match _type[0]: + TYPE_BOOL: return "any_bool()" + TYPE_STRING, TYPE_STRING_NAME: return "any_string()" + TYPE_INT: return "any_int()" + TYPE_FLOAT: return "any_float()" + TYPE_COLOR: return "any_color()" + TYPE_VECTOR2: return "any_vector2()" if _type.size() == 1 else "any_vector()" + TYPE_VECTOR2I: return "any_vector2i()" + TYPE_VECTOR3: return "any_vector3()" + TYPE_VECTOR3I: return "any_vector3i()" + TYPE_VECTOR4: return "any_vector4()" + TYPE_VECTOR4I: return "any_vector4i()" + TYPE_RECT2: return "any_rect2()" + TYPE_RECT2I: return "any_rect2i()" + TYPE_PLANE: return "any_plane()" + TYPE_QUATERNION: return "any_quat()" + TYPE_AABB: return "any_aabb()" + TYPE_BASIS: return "any_basis()" + TYPE_TRANSFORM2D: return "any_transform_2d()" + TYPE_TRANSFORM3D: return "any_transform_3d()" + TYPE_NODE_PATH: return "any_node_path()" + TYPE_RID: return "any_rid()" + TYPE_OBJECT: return "any_object()" + TYPE_DICTIONARY: return "any_dictionary()" + TYPE_ARRAY: return "any_array()" + TYPE_PACKED_BYTE_ARRAY: return "any_packed_byte_array()" + TYPE_PACKED_INT32_ARRAY: return "any_packed_int32_array()" + TYPE_PACKED_INT64_ARRAY: return "any_packed_int64_array()" + TYPE_PACKED_FLOAT32_ARRAY: return "any_packed_float32_array()" + TYPE_PACKED_FLOAT64_ARRAY: return "any_packed_float64_array()" + TYPE_PACKED_STRING_ARRAY: return "any_packed_string_array()" + TYPE_PACKED_VECTOR2_ARRAY: return "any_packed_vector2_array()" + TYPE_PACKED_VECTOR3_ARRAY: return "any_packed_vector3_array()" + TYPE_PACKED_COLOR_ARRAY: return "any_packed_color_array()" + _: return "any()" diff --git a/addons/gdUnit4/src/matchers/AnyClazzArgumentMatcher.gd b/addons/gdUnit4/src/matchers/AnyClazzArgumentMatcher.gd index f553bfe0..ec148fde 100644 --- a/addons/gdUnit4/src/matchers/AnyClazzArgumentMatcher.gd +++ b/addons/gdUnit4/src/matchers/AnyClazzArgumentMatcher.gd @@ -3,7 +3,7 @@ extends GdUnitArgumentMatcher var _clazz :Object -func _init(clazz :Object): +func _init(clazz :Object) -> void: _clazz = clazz func is_match(value :Variant) -> bool: @@ -12,3 +12,17 @@ func is_match(value :Variant) -> bool: if is_instance_valid(value) and GdObjects.is_script(_clazz): return value.get_script() == _clazz return is_instance_of(value, _clazz) + + +func _to_string() -> String: + if (_clazz as Object).is_class("GDScriptNativeClass"): + var instance :Object = _clazz.new() + var clazz_name := instance.get_class() + if not instance is RefCounted: + instance.free() + return "any_class(<"+clazz_name+">)"; + if _clazz is GDScript: + var result := GdObjects.extract_class_name(_clazz) + if result.is_success(): + return "any_class(<"+ result.value() + ">)" + return "any_class()" diff --git a/addons/gdUnit4/src/matchers/ChainedArgumentMatcher.gd b/addons/gdUnit4/src/matchers/ChainedArgumentMatcher.gd index e42de50d..cb0e4323 100644 --- a/addons/gdUnit4/src/matchers/ChainedArgumentMatcher.gd +++ b/addons/gdUnit4/src/matchers/ChainedArgumentMatcher.gd @@ -3,15 +3,18 @@ extends GdUnitArgumentMatcher var _matchers :Array + func _init(matchers :Array): _matchers = matchers -func is_match(arguments :Array) -> bool: - if arguments.size() != _matchers.size(): + +func is_match(arguments :Variant) -> bool: + var arg_array := arguments as Array + if arg_array.size() != _matchers.size(): return false - for index in arguments.size(): - var arg = arguments[index] + for index in arg_array.size(): + var arg :Variant = arg_array[index] var matcher = _matchers[index] as GdUnitArgumentMatcher if not matcher.is_match(arg): diff --git a/addons/gdUnit4/src/matchers/GdUnitArgumentMatcher.gd b/addons/gdUnit4/src/matchers/GdUnitArgumentMatcher.gd index dff6dac2..aa43b80f 100644 --- a/addons/gdUnit4/src/matchers/GdUnitArgumentMatcher.gd +++ b/addons/gdUnit4/src/matchers/GdUnitArgumentMatcher.gd @@ -4,5 +4,5 @@ extends RefCounted @warning_ignore("unused_parameter") -func is_match(value) -> bool: +func is_match(value :Variant) -> bool: return true diff --git a/addons/gdUnit4/src/matchers/GdUnitArgumentMatchers.gd b/addons/gdUnit4/src/matchers/GdUnitArgumentMatchers.gd index 24e6e855..ddd58f11 100644 --- a/addons/gdUnit4/src/matchers/GdUnitArgumentMatchers.gd +++ b/addons/gdUnit4/src/matchers/GdUnitArgumentMatchers.gd @@ -4,8 +4,8 @@ extends RefCounted const TYPE_ANY = TYPE_MAX + 100 -static func to_matcher(arguments :Array, auto_deep_check_mode := false) -> ChainedArgumentMatcher: - var matchers := Array() +static func to_matcher(arguments :Array[Variant], auto_deep_check_mode := false) -> ChainedArgumentMatcher: + var matchers :Array[Variant] = [] for arg in arguments: # argument is already a matcher if arg is GdUnitArgumentMatcher: @@ -28,5 +28,5 @@ static func by_types(types :PackedInt32Array) -> GdUnitArgumentMatcher: return AnyBuildInTypeArgumentMatcher.new(types) -static func any_class(clazz) -> GdUnitArgumentMatcher: +static func any_class(clazz :Object) -> GdUnitArgumentMatcher: return AnyClazzArgumentMatcher.new(clazz) diff --git a/addons/gdUnit4/src/mocking/GdUnitMock.gd b/addons/gdUnit4/src/mocking/GdUnitMock.gd index ba993226..07a37bee 100644 --- a/addons/gdUnit4/src/mocking/GdUnitMock.gd +++ b/addons/gdUnit4/src/mocking/GdUnitMock.gd @@ -9,10 +9,10 @@ const RETURN_DEFAULTS = "RETURN_DEFAULTS" ## builds full deep mocked object const RETURN_DEEP_STUB = "RETURN_DEEP_STUB" -var _value +var _value :Variant -func _init(value): +func _init(value :Variant) -> void: _value = value @@ -28,7 +28,7 @@ func on(obj :Object) -> Object: ## [color=yellow]`checked` is obsolete, use `on` instead [/color] -func checked(obj :Object) -> Object: +func checked(obj :Object) -> Object: push_warning("Using a deprecated function 'checked' use `on` instead") return on(obj) diff --git a/addons/gdUnit4/src/mocking/GdUnitMockBuilder.gd b/addons/gdUnit4/src/mocking/GdUnitMockBuilder.gd index df7f02c3..1fc378d3 100644 --- a/addons/gdUnit4/src/mocking/GdUnitMockBuilder.gd +++ b/addons/gdUnit4/src/mocking/GdUnitMockBuilder.gd @@ -96,7 +96,7 @@ static func mock_on_script(instance :Object, clazz :Variant, function_excludes : var mock := GDScript.new() mock.source_code = "\n".join(lines) mock.resource_name = "Mock%s.gd" % clazz_name - mock.resource_path = GdUnitTools.create_temp_dir("mock") + "/Mock%s_%d.gd" % [clazz_name, Time.get_ticks_msec()] + mock.resource_path = GdUnitFileAccess.create_temp_dir("mock") + "/Mock%s_%d.gd" % [clazz_name, Time.get_ticks_msec()] if debug_write: DirAccess.remove_absolute(mock.resource_path) diff --git a/addons/gdUnit4/src/mocking/GdUnitMockImpl.gd b/addons/gdUnit4/src/mocking/GdUnitMockImpl.gd index 94110c28..aa0d6646 100644 --- a/addons/gdUnit4/src/mocking/GdUnitMockImpl.gd +++ b/addons/gdUnit4/src/mocking/GdUnitMockImpl.gd @@ -7,7 +7,7 @@ const __SOURCE_CLASS = "${source_class}" var __working_mode := GdUnitMock.RETURN_DEFAULTS var __excluded_methods :PackedStringArray = [] -var __do_return_value = null +var __do_return_value :Variant = null var __prepare_return_value := false #{ = { @@ -17,11 +17,11 @@ var __prepare_return_value := false var __mocked_return_values := Dictionary() -static func __instance(): +static func __instance() -> Object: return Engine.get_meta(__INSTANCE_ID) -func _notification(what): +func _notification(what :int) -> void: if what == NOTIFICATION_PREDELETE: if Engine.has_meta(__INSTANCE_ID): Engine.remove_meta(__INSTANCE_ID) @@ -31,12 +31,12 @@ func __instance_id() -> String: return __INSTANCE_ID -func __set_singleton(): +func __set_singleton() -> void: # store self need to mock static functions Engine.set_meta(__INSTANCE_ID, self) -func __release_double(): +func __release_double() -> void: # we need to release the self reference manually to prevent orphan nodes Engine.remove_meta(__INSTANCE_ID) @@ -46,7 +46,8 @@ func __is_prepare_return_value() -> bool: func __sort_by_argument_matcher(left_args :Array, _right_args :Array) -> bool: - for larg in left_args: + for index in left_args.size(): + var larg :Variant = left_args[index] if larg is GdUnitArgumentMatcher: return false return true @@ -60,12 +61,13 @@ func __sort_dictionary(unsorted_args :Dictionary) -> Dictionary: var sorted_args := unsorted_args.keys() sorted_args.sort_custom(__sort_by_argument_matcher) var sorted_result := {} - for key in sorted_args: + for index in sorted_args.size(): + var key :Variant = sorted_args[index] sorted_result[key] = unsorted_args[key] return sorted_result -func __save_function_return_value(args :Array): +func __save_function_return_value(args :Array) -> void: var func_name :String = args[0] var func_args :Array = args.slice(1) var mocked_return_value_by_args :Dictionary = __mocked_return_values.get(func_name, {}) @@ -77,13 +79,14 @@ func __save_function_return_value(args :Array): func __is_mocked_args_match(func_args :Array, mocked_args :Array) -> bool: var is_matching := false - for args in mocked_args: + for index in mocked_args.size(): + var args :Variant = mocked_args[index] if func_args.size() != args.size(): continue is_matching = true for arg_index in func_args.size(): - var func_arg = func_args[arg_index] - var mock_arg = args[arg_index] + var func_arg :Variant = func_args[arg_index] + var mock_arg :Variant = args[arg_index] if mock_arg is GdUnitArgumentMatcher: is_matching = is_matching and mock_arg.is_match(func_arg) else: @@ -101,7 +104,8 @@ func __get_mocked_return_value_or_default(args :Array, default_return_value :Var return default_return_value var func_args :Array = args.slice(1) var mocked_args :Array = __mocked_return_values.get(func_name).keys() - for margs in mocked_args: + for index in mocked_args.size(): + var margs :Variant = mocked_args[index] if __is_mocked_args_match(func_args, [margs]): return __mocked_return_values[func_name][margs] return default_return_value @@ -111,7 +115,7 @@ func __set_script(script :GDScript) -> void: super.set_script(script) -func __set_mode(working_mode :String): +func __set_mode(working_mode :String) -> Object: __working_mode = working_mode return self @@ -130,7 +134,7 @@ func __exclude_method_call(exluded_methods :PackedStringArray) -> void: __excluded_methods.append_array(exluded_methods) -func __do_return(return_value): +func __do_return(return_value :Variant) -> Object: __do_return_value = return_value __prepare_return_value = true return self diff --git a/addons/gdUnit4/src/monitor/GodotGdErrorMonitor.gd b/addons/gdUnit4/src/monitor/GodotGdErrorMonitor.gd index 47250b65..967dc7e2 100644 --- a/addons/gdUnit4/src/monitor/GodotGdErrorMonitor.gd +++ b/addons/gdUnit4/src/monitor/GodotGdErrorMonitor.gd @@ -4,32 +4,30 @@ extends GdUnitMonitor var _godot_log_file :String var _eof :int var _report_enabled := false -var _report_force : bool +var _entries: Array[ErrorLogEntry] = [] -func _init(force := false): +func _init(): super("GodotGdErrorMonitor") - _report_force = force _godot_log_file = GdUnitSettings.get_log_path() + _report_enabled = _is_reporting_enabled() func start(): - _report_enabled = _report_force or is_reporting_enabled() - if _report_enabled: - var file = FileAccess.open(_godot_log_file, FileAccess.READ) - if file: - file.seek_end(0) - _eof = file.get_length() + var file = FileAccess.open(_godot_log_file, FileAccess.READ) + if file: + file.seek_end(0) + _eof = file.get_length() func stop(): pass -func reports() -> Array[GdUnitReport]: +func to_reports() -> Array[GdUnitReport]: var reports_ :Array[GdUnitReport] = [] if _report_enabled: - reports_.assign(_collect_log_entries().map(_to_report)) + reports_.assign(_entries.map(_to_report)) return reports_ @@ -42,35 +40,45 @@ static func _to_report(errorLog :ErrorLogEntry) -> GdUnitReport: return GdUnitReport.new().create(GdUnitReport.ABORT, errorLog._line, failure) -func scan() -> Array[ErrorLogEntry]: +func scan(force_collect_reports := false) -> Array[ErrorLogEntry]: await Engine.get_main_loop().process_frame - return _collect_log_entries() + await Engine.get_main_loop().physics_frame + _entries.append_array(_collect_log_entries(force_collect_reports)) + return _entries + + +func erase_log_entry(entry :ErrorLogEntry) -> void: + _entries.erase(entry) -func _collect_log_entries() -> Array[ErrorLogEntry]: +func _collect_log_entries(force_collect_reports :bool) -> Array[ErrorLogEntry]: var file = FileAccess.open(_godot_log_file, FileAccess.READ) file.seek(_eof) var records := PackedStringArray() while not file.eof_reached(): records.append(file.get_line()) + file.seek_end(0) + _eof = file.get_length() var log_entries :Array[ErrorLogEntry]= [] + var is_report_errors := force_collect_reports or _is_report_push_errors() + var is_report_script_errors := force_collect_reports or _is_report_script_errors() for index in records.size(): - if _report_force: + if force_collect_reports: log_entries.append(ErrorLogEntry.extract_push_warning(records, index)) - if _is_report_push_errors(): + if is_report_errors: log_entries.append(ErrorLogEntry.extract_push_error(records, index)) - if _is_report_script_errors(): + if is_report_script_errors: log_entries.append(ErrorLogEntry.extract_error(records, index)) return log_entries.filter(func(value): return value != null ) -func is_reporting_enabled() -> bool: +func _is_reporting_enabled() -> bool: return _is_report_script_errors() or _is_report_push_errors() func _is_report_push_errors() -> bool: - return _report_force or GdUnitSettings.is_report_push_errors() + return GdUnitSettings.is_report_push_errors() func _is_report_script_errors() -> bool: - return _report_force or GdUnitSettings.is_report_script_errors() + return GdUnitSettings.is_report_script_errors() diff --git a/addons/gdUnit4/src/mono/GdUnit4CSharpApi.cs b/addons/gdUnit4/src/mono/GdUnit4CSharpApi.cs new file mode 100644 index 00000000..6c27132c --- /dev/null +++ b/addons/gdUnit4/src/mono/GdUnit4CSharpApi.cs @@ -0,0 +1,19 @@ +using Godot; +using Godot.Collections; + +using GdUnit4; + +// GdUnit4 GDScript - C# API wrapper +public partial class GdUnit4CSharpApi : RefCounted +{ + public static string Version() => GdUnit4MonoAPI.Version(); + + public static bool IsTestSuite(string classPath) => GdUnit4MonoAPI.IsTestSuite(classPath); + + public static RefCounted Executor(Node listener) => (RefCounted)GdUnit4MonoAPI.Executor(listener); + + public static GdUnit4.CsNode? ParseTestSuite(string classPath) => GdUnit4MonoAPI.ParseTestSuite(classPath); + + public static Dictionary CreateTestSuite(string sourcePath, int lineNumber, string testSuitePath) => + GdUnit4MonoAPI.CreateTestSuite(sourcePath, lineNumber, testSuitePath); +} diff --git a/addons/gdUnit4/src/mono/GdUnit4MonoApiLoader.gd b/addons/gdUnit4/src/mono/GdUnit4CSharpApiLoader.gd similarity index 67% rename from addons/gdUnit4/src/mono/GdUnit4MonoApiLoader.gd rename to addons/gdUnit4/src/mono/GdUnit4CSharpApiLoader.gd index 4ad97a48..2eff8c96 100644 --- a/addons/gdUnit4/src/mono/GdUnit4MonoApiLoader.gd +++ b/addons/gdUnit4/src/mono/GdUnit4CSharpApiLoader.gd @@ -1,18 +1,17 @@ extends RefCounted -class_name GdUnit4MonoApiLoader +class_name GdUnit4CSharpApiLoader static func instance() -> Object: - return GdUnitSingleton.instance("GdUnit4MonoAPI", func(): - if not GdUnit4MonoApiLoader.is_mono_supported(): + return GdUnitSingleton.instance("GdUnit4CSharpApi", func() -> Object: + if not GdUnit4CSharpApiLoader.is_mono_supported(): return null - var GdUnit4MonoApi = load("res://addons/gdUnit4/src/mono/GdUnit4MonoApi.cs") - return GdUnit4MonoApi.new() + return load("res://addons/gdUnit4/src/mono/GdUnit4CSharpApi.cs") ) static func is_engine_version_supported(engine_version :int = Engine.get_version_info().hex) -> bool: - return engine_version >= 0x40100 + return engine_version >= 0x40200 # test is Godot mono running @@ -21,14 +20,14 @@ static func is_mono_supported() -> bool: static func version() -> String: - if not GdUnit4MonoApiLoader.is_mono_supported(): + if not GdUnit4CSharpApiLoader.is_mono_supported(): return "unknown" return instance().Version() static func create_test_suite(source_path :String, line_number :int, test_suite_path :String) -> GdUnitResult: - if not GdUnit4MonoApiLoader.is_mono_supported(): - return GdUnitResult.error("Can't create test suite. No c# support found.") + if not GdUnit4CSharpApiLoader.is_mono_supported(): + return GdUnitResult.error("Can't create test suite. No C# support found.") var result := instance().CreateTestSuite(source_path, line_number, test_suite_path) as Dictionary if result.has("error"): return GdUnitResult.error(result.get("error")) @@ -36,8 +35,9 @@ static func create_test_suite(source_path :String, line_number :int, test_suite_ static func is_test_suite(resource_path :String) -> bool: - if not is_csharp_file(resource_path) or not GdUnit4MonoApiLoader.is_mono_supported(): + if not is_csharp_file(resource_path) or not GdUnit4CSharpApiLoader.is_mono_supported(): return false + if resource_path.is_empty(): if GdUnitSettings.is_report_push_errors(): push_error("Can't create test suite. Missing resource path.") @@ -46,7 +46,7 @@ static func is_test_suite(resource_path :String) -> bool: static func parse_test_suite(source_path :String) -> Node: - if not GdUnit4MonoApiLoader.is_mono_supported(): + if not GdUnit4CSharpApiLoader.is_mono_supported(): if GdUnitSettings.is_report_push_errors(): push_error("Can't create test suite. No c# support found.") return null @@ -54,11 +54,11 @@ static func parse_test_suite(source_path :String) -> Node: static func create_executor(listener :Node) -> RefCounted: - if not GdUnit4MonoApiLoader.is_mono_supported(): + if not GdUnit4CSharpApiLoader.is_mono_supported(): return null return instance().Executor(listener) static func is_csharp_file(resource_path :String) -> bool: var ext := resource_path.get_extension() - return ext == "cs" and GdUnit4MonoApiLoader.is_mono_supported() + return ext == "cs" and GdUnit4CSharpApiLoader.is_mono_supported() diff --git a/addons/gdUnit4/src/mono/GdUnit4MonoApi.cs b/addons/gdUnit4/src/mono/GdUnit4MonoApi.cs deleted file mode 100644 index 0563ef92..00000000 --- a/addons/gdUnit4/src/mono/GdUnit4MonoApi.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Godot; -using Godot.Collections; - -// GdUnit4 C# API wrapper -public partial class GdUnit4MonoApi : GdUnit4.GdUnit4MonoAPI -{ - public new string Version() => GdUnit4.GdUnit4MonoAPI.Version(); - - public new bool IsTestSuite(string classPath) => GdUnit4.GdUnit4MonoAPI.IsTestSuite(classPath); - - public new RefCounted Executor(Node listener) => (RefCounted)GdUnit4.GdUnit4MonoAPI.Executor(listener); - - public new GdUnit4.CsNode? ParseTestSuite(string classPath) => GdUnit4.GdUnit4MonoAPI.ParseTestSuite(classPath); - - public new Dictionary CreateTestSuite(string sourcePath, int lineNumber, string testSuitePath) => - GdUnit4.GdUnit4MonoAPI.CreateTestSuite(sourcePath, lineNumber, testSuitePath); -} diff --git a/addons/gdUnit4/src/network/rpc/RPC.gd b/addons/gdUnit4/src/network/rpc/RPC.gd index 93d5462a..89bd8e36 100644 --- a/addons/gdUnit4/src/network/rpc/RPC.gd +++ b/addons/gdUnit4/src/network/rpc/RPC.gd @@ -5,13 +5,13 @@ func serialize() -> String: return JSON.stringify(inst_to_dict(self)) # using untyped version see comments below -static func deserialize(json_value :String): +static func deserialize(json_value :String) -> Object: var json := JSON.new() var err := json.parse(json_value) if err != OK: push_error("Can't deserialize JSON, error at line %d: %s \n json: '%s'" % [json.get_error_line(), json.get_error_message(), json_value]) return null - var result = json.get_data() as Dictionary + var result := json.get_data() as Dictionary if not typeof(result) == TYPE_DICTIONARY: push_error("Can't deserialize JSON, error at line %d: %s \n json: '%s'" % [result.error_line, result.error_string, json_value]) return null diff --git a/addons/gdUnit4/src/network/rpc/RPCGdUnitTestSuite.gd b/addons/gdUnit4/src/network/rpc/RPCGdUnitTestSuite.gd index d8d8b00e..ed2fff5e 100644 --- a/addons/gdUnit4/src/network/rpc/RPCGdUnitTestSuite.gd +++ b/addons/gdUnit4/src/network/rpc/RPCGdUnitTestSuite.gd @@ -3,13 +3,13 @@ extends RPC var _data :Dictionary -static func of(test_suite) -> RPCGdUnitTestSuite: - var rpc = RPCGdUnitTestSuite.new() +static func of(test_suite :Node) -> RPCGdUnitTestSuite: + var rpc := RPCGdUnitTestSuite.new() rpc._data = GdUnitTestSuiteDto.new().serialize(test_suite) return rpc func dto() -> GdUnitResourceDto: return GdUnitTestSuiteDto.new().deserialize(_data) -func _to_string(): +func _to_string() -> String: return "RPCGdUnitTestSuite: " + str(_data) diff --git a/addons/gdUnit4/src/network/rpc/dtos/GdUnitResourceDto.gd b/addons/gdUnit4/src/network/rpc/dtos/GdUnitResourceDto.gd index d2cea382..9152c8d2 100644 --- a/addons/gdUnit4/src/network/rpc/dtos/GdUnitResourceDto.gd +++ b/addons/gdUnit4/src/network/rpc/dtos/GdUnitResourceDto.gd @@ -4,19 +4,23 @@ extends Resource var _name :String var _path :String -func serialize(resource) -> Dictionary: + +func serialize(resource :Node) -> Dictionary: var serialized := Dictionary() serialized["name"] = resource.get_name() serialized["resource_path"] = resource.ResourcePath() return serialized + func deserialize(data :Dictionary) -> GdUnitResourceDto: _name = data.get("name", "n.a.") _path = data.get("resource_path", "") return self + func name() -> String: return _name + func path() -> String: return _path diff --git a/addons/gdUnit4/src/network/rpc/dtos/GdUnitTestCaseDto.gd b/addons/gdUnit4/src/network/rpc/dtos/GdUnitTestCaseDto.gd index d30a0696..26f5dda5 100644 --- a/addons/gdUnit4/src/network/rpc/dtos/GdUnitTestCaseDto.gd +++ b/addons/gdUnit4/src/network/rpc/dtos/GdUnitTestCaseDto.gd @@ -5,7 +5,7 @@ var _line_number :int = -1 var _test_case_names :PackedStringArray = [] -func serialize(test_case :Object) -> Dictionary: +func serialize(test_case :Node) -> Dictionary: var serialized := super.serialize(test_case) if test_case.has_method("line_number"): serialized["line_number"] = test_case.line_number() diff --git a/addons/gdUnit4/src/network/rpc/dtos/GdUnitTestSuiteDto.gd b/addons/gdUnit4/src/network/rpc/dtos/GdUnitTestSuiteDto.gd index fb93edc5..9ecc9f68 100644 --- a/addons/gdUnit4/src/network/rpc/dtos/GdUnitTestSuiteDto.gd +++ b/addons/gdUnit4/src/network/rpc/dtos/GdUnitTestSuiteDto.gd @@ -15,13 +15,13 @@ func serialize(test_suite :Node) -> Dictionary: func deserialize(data :Dictionary) -> GdUnitResourceDto: super.deserialize(data) - var test_cases_ :Array = data.get("test_cases", Array()) + var test_cases_ :Array = data.get("test_cases", []) for test_case in test_cases_: add_test_case(GdUnitTestCaseDto.new().deserialize(test_case)) return self -func add_test_case(test_case :GdUnitTestCaseDto): +func add_test_case(test_case :GdUnitTestCaseDto) -> void: _test_cases_by_name[test_case.name()] = test_case diff --git a/addons/gdUnit4/src/report/GdUnitHtmlReport.gd b/addons/gdUnit4/src/report/GdUnitHtmlReport.gd index 41ea52a7..050a0065 100644 --- a/addons/gdUnit4/src/report/GdUnitHtmlReport.gd +++ b/addons/gdUnit4/src/report/GdUnitHtmlReport.gd @@ -8,7 +8,7 @@ var _iteration :int func _init(path_ :String): - _iteration = GdUnitTools.find_last_path_index(path_, REPORT_DIR_PREFIX) + 1 + _iteration = GdUnitFileAccess.find_last_path_index(path_, REPORT_DIR_PREFIX) + 1 _report_path = "%s/%s%d" % [path_, REPORT_DIR_PREFIX, _iteration] DirAccess.make_dir_recursive_absolute(_report_path) @@ -59,12 +59,12 @@ func write() -> String: # write report var index_file := "%s/index.html" % _report_path FileAccess.open(index_file, FileAccess.WRITE).store_string(to_write) - GdUnitTools.copy_directory("res://addons/gdUnit4/src/report/template/css/", _report_path + "/css") + GdUnitFileAccess.copy_directory("res://addons/gdUnit4/src/report/template/css/", _report_path + "/css") return index_file func delete_history(max_reports :int) -> int: - return GdUnitTools.delete_path_index_lower_equals_than(_report_path.get_base_dir(), REPORT_DIR_PREFIX, _iteration-max_reports) + return GdUnitFileAccess.delete_path_index_lower_equals_than(_report_path.get_base_dir(), REPORT_DIR_PREFIX, _iteration-max_reports) func apply_path_reports(report_dir :String, template :String, reports_ :Array) -> String: diff --git a/addons/gdUnit4/src/spy/GdUnitSpyBuilder.gd b/addons/gdUnit4/src/spy/GdUnitSpyBuilder.gd index b01350ae..2db29bd7 100644 --- a/addons/gdUnit4/src/spy/GdUnitSpyBuilder.gd +++ b/addons/gdUnit4/src/spy/GdUnitSpyBuilder.gd @@ -5,7 +5,7 @@ const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd") const SPY_TEMPLATE :GDScript = preload("res://addons/gdUnit4/src/spy/GdUnitSpyImpl.gd") -static func build(to_spy, debug_write = false) -> Object: +static func build(to_spy, debug_write := false) -> Object: if GdObjects.is_singleton(to_spy): push_error("Spy on a Singleton is not allowed! '%s'" % to_spy.get_class()) return null @@ -61,7 +61,7 @@ static func spy_on_script(instance, function_excludes :PackedStringArray, debug_ var spy := GDScript.new() spy.source_code = "\n".join(lines) spy.resource_name = "Spy%s.gd" % clazz_name - spy.resource_path = GdUnitTools.create_temp_dir("spy") + "/Spy%s_%d.gd" % [clazz_name, Time.get_ticks_msec()] + spy.resource_path = GdUnitFileAccess.create_temp_dir("spy") + "/Spy%s_%d.gd" % [clazz_name, Time.get_ticks_msec()] if debug_write: DirAccess.remove_absolute(spy.resource_path) diff --git a/addons/gdUnit4/src/spy/GdUnitSpyImpl.gd b/addons/gdUnit4/src/spy/GdUnitSpyImpl.gd index 8b75a3e0..3b61e869 100644 --- a/addons/gdUnit4/src/spy/GdUnitSpyImpl.gd +++ b/addons/gdUnit4/src/spy/GdUnitSpyImpl.gd @@ -2,7 +2,7 @@ const __INSTANCE_ID = "${instance_id}" const __SOURCE_CLASS = "${source_class}" -var __instance_delegator +var __instance_delegator :Object var __excluded_methods :PackedStringArray = [] @@ -10,7 +10,7 @@ static func __instance() -> Variant: return Engine.get_meta(__INSTANCE_ID) -func _notification(what): +func _notification(what :int) -> void: if what == NOTIFICATION_PREDELETE: if Engine.has_meta(__INSTANCE_ID): Engine.remove_meta(__INSTANCE_ID) @@ -20,13 +20,13 @@ func __instance_id() -> String: return __INSTANCE_ID -func __set_singleton(delegator): +func __set_singleton(delegator :Object) -> void: # store self need to mock static functions Engine.set_meta(__INSTANCE_ID, self) __instance_delegator = delegator -func __release_double(): +func __release_double() -> void: # we need to release the self reference manually to prevent orphan nodes Engine.remove_meta(__INSTANCE_ID) __instance_delegator = null @@ -40,5 +40,5 @@ func __exclude_method_call(exluded_methods :PackedStringArray) -> void: __excluded_methods.append_array(exluded_methods) -func __call_func(func_name :String, arguments :Array): +func __call_func(func_name :String, arguments :Array) -> Variant: return __instance_delegator.callv(func_name, arguments) diff --git a/addons/gdUnit4/src/ui/GdUnitFonts.gd b/addons/gdUnit4/src/ui/GdUnitFonts.gd index 206b8752..46ee80c9 100644 --- a/addons/gdUnit4/src/ui/GdUnitFonts.gd +++ b/addons/gdUnit4/src/ui/GdUnitFonts.gd @@ -16,7 +16,7 @@ static func init_fonts(item: CanvasItem) -> float: var plugin :EditorPlugin = Engine.get_meta("GdUnitEditorPlugin") var settings := plugin.get_editor_interface().get_editor_settings() var scale_factor := plugin.get_editor_interface().get_editor_scale() - var font_size = settings.get_setting("interface/editor/main_font_size") + var font_size :float = settings.get_setting("interface/editor/main_font_size") font_size *= scale_factor var font_mono := load_and_resize_font(FONT_MONO, font_size) item.set("theme_override_fonts/normal_font", font_mono) @@ -39,6 +39,6 @@ static func load_and_resize_font(font_resource: String, size: float) -> Font: if font == null: push_error("Can't load font '%s'" % font_resource) return null - var resized_font = font.duplicate() + var resized_font := font.duplicate() resized_font.fixed_size = int(size) return resized_font diff --git a/addons/gdUnit4/src/ui/settings/GdUnitSettingsDialog.gd b/addons/gdUnit4/src/ui/settings/GdUnitSettingsDialog.gd index 8d2f1fad..8ea55fd5 100644 --- a/addons/gdUnit4/src/ui/settings/GdUnitSettingsDialog.gd +++ b/addons/gdUnit4/src/ui/settings/GdUnitSettingsDialog.gd @@ -174,7 +174,7 @@ func _install_examples() -> void: _init_progress(5) update_progress("Downloading examples") await get_tree().process_frame - var tmp_path := GdUnitTools.create_temp_dir("download") + var tmp_path := GdUnitFileAccess.create_temp_dir("download") var zip_file := tmp_path + "/examples.zip" var response :GdUnitUpdateClient.HttpResponse = await _update_client.request_zip_package(EAXAMPLE_URL, zip_file) if response.code() != 200: @@ -185,7 +185,7 @@ func _install_examples() -> void: return # extract zip to tmp update_progress("Install examples into project") - var result := GdUnitTools.extract_zip(zip_file, "res://gdUnit4-examples/") + var result := GdUnitFileAccess.extract_zip(zip_file, "res://gdUnit4-examples/") if result.is_error(): update_progress("Install examples failed! %s" % result.error_message()) await get_tree().create_timer(3).timeout diff --git a/addons/gdUnit4/src/ui/templates/TestSuiteTemplate.gd b/addons/gdUnit4/src/ui/templates/TestSuiteTemplate.gd index ffa316f4..b186e7c4 100644 --- a/addons/gdUnit4/src/ui/templates/TestSuiteTemplate.gd +++ b/addons/gdUnit4/src/ui/templates/TestSuiteTemplate.gd @@ -6,15 +6,15 @@ extends MarginContainer @onready var _title_bar :Panel = $VBoxContainer/sub_category @onready var _save_button :Button = $VBoxContainer/Panel/HBoxContainer/Save @onready var _selected_type :OptionButton = $VBoxContainer/EdiorLayout/Editor/MarginContainer/HBoxContainer/SelectType -@onready var _show_tags = $Tags +@onready var _show_tags :PopupPanel = $Tags -var gd_key_words := ["extends", "class_name", "const", "var", "onready", "func", "void", "pass"] -var gdunit_key_words := ["GdUnitTestSuite", "before", "after", "before_test", "after_test"] +var gd_key_words :PackedStringArray = ["extends", "class_name", "const", "var", "onready", "func", "void", "pass"] +var gdunit_key_words :PackedStringArray = ["GdUnitTestSuite", "before", "after", "before_test", "after_test"] var _selected_template :int -func _ready(): +func _ready() -> void: setup_editor_colors() setup_fonts() setup_supported_types() @@ -22,7 +22,7 @@ func _ready(): setup_tags_help() -func _notification(what): +func _notification(what :int) -> void: if what == EditorSettings.NOTIFICATION_EDITOR_SETTINGS_CHANGED: setup_fonts() @@ -78,7 +78,7 @@ func setup_highlighter(editor :CodeEdit, settings :EditorSettings) -> void: func setup_fonts() -> void: if _template_editor: GdUnitFonts.init_fonts(_template_editor) - var font_size = GdUnitFonts.init_fonts(_tags_editor) + var font_size := GdUnitFonts.init_fonts(_tags_editor) _title_bar.size.y = font_size + 16 _title_bar.custom_minimum_size.y = font_size + 16 @@ -98,25 +98,25 @@ func load_template(template_id :int) -> void: _template_editor.set_text(GdUnitTestSuiteTemplate.load_template(template_id)) -func _on_Restore_pressed(): +func _on_Restore_pressed() -> void: _template_editor.set_text(GdUnitTestSuiteTemplate.default_template(_selected_template)) GdUnitTestSuiteTemplate.reset_to_default(_selected_template) _save_button.disabled = true -func _on_Save_pressed(): +func _on_Save_pressed() -> void: GdUnitTestSuiteTemplate.save_template(_selected_template, _template_editor.get_text()) _save_button.disabled = true -func _on_Tags_pressed(): +func _on_Tags_pressed() -> void: _show_tags.popup_centered_ratio(.5) -func _on_Editor_text_changed(): +func _on_Editor_text_changed() -> void: _save_button.disabled = false -func _on_SelectType_item_selected(index): +func _on_SelectType_item_selected(index :int) -> void: load_template(_selected_type.get_item_id(index)) setup_tags_help() diff --git a/addons/gdUnit4/src/update/GdUnitUpdate.gd b/addons/gdUnit4/src/update/GdUnitUpdate.gd index dc2c0bec..3d78a50d 100644 --- a/addons/gdUnit4/src/update/GdUnitUpdate.gd +++ b/addons/gdUnit4/src/update/GdUnitUpdate.gd @@ -211,7 +211,7 @@ func extract_zip(zip_package :String, dest_path :String) -> Variant: func download_release() -> void: - var zip_file := GdUnitTools.temp_dir() + "/update.zip" + var zip_file := GdUnitFileAccess.temp_dir() + "/update.zip" var response :GdUnitUpdateClient.HttpResponse if _debug_mode: response = GdUnitUpdateClient.HttpResponse.new(200, PackedByteArray()) diff --git a/docs/.gdignore b/docs/.gdignore new file mode 100644 index 00000000..e69de29b diff --git a/docs/assets/blackboard_compare_advanced.png b/docs/assets/blackboard_compare_advanced.png new file mode 100644 index 00000000..30a88fc0 Binary files /dev/null and b/docs/assets/blackboard_compare_advanced.png differ diff --git a/docs/assets/blackboard_compare_equal.png b/docs/assets/blackboard_compare_equal.png new file mode 100644 index 00000000..ce9f68cb Binary files /dev/null and b/docs/assets/blackboard_compare_equal.png differ diff --git a/docs/manual/action_leaf.md b/docs/manual/action_leaf.md index 21790056..ef863cb6 100644 --- a/docs/manual/action_leaf.md +++ b/docs/manual/action_leaf.md @@ -25,3 +25,12 @@ In another example, you have a character that can fish. You can use an `ActionLe 3. If the character is interrupted or fails to catch a fish, return `FAILURE` Combine `ActionLeaf` nodes with other Behavior Tree nodes to create complex actions and behaviors for your game characters. + +## Blackboard Action Nodes +Beehave provides some nodes for common Blackboard actions out of the box. + +### Blackboard Erase +The `BeehaveBlackboardErase` node erases a specified key from the blackboard. + +### Blackboard Set +The `BeehaveBlackboardSet` node sets a specified key to a specified value. The value is an expression and can thus be of any type, like a number, a `String`, a `bool`, etc. \ No newline at end of file diff --git a/docs/manual/condition_leaf.md b/docs/manual/condition_leaf.md index 05bd3567..51a41f56 100644 --- a/docs/manual/condition_leaf.md +++ b/docs/manual/condition_leaf.md @@ -32,3 +32,18 @@ func tick(actor: Node, _blackboard: Blackboard) -> int: return FAILURE ``` In this example, we have extended the `ConditionLeaf` to create `IsPlayerWithinAttackRange`. This condition could be a part of a behavior tree used by an enemy, with the `Node` referenced by the `actor` parameter representing the enemy. Our enemy node provides access to a `get_distance_from_player` and a `get_attack_range` function, which are used to determine if the player is within attack range. If `distance_from_player` is less than or equal to `enemy_attack_range`, we return `SUCCESS`, and the behavior tree proceeds to execute an attack sequence. If `distance_from_player` is greater than `enemy_attack_range`, we return `FAILURE`, and the attack sequence is not executed. + +## Blackboard Condition Nodes +Beehave provides some nodes for common and useful Blackboard condition checks. + +### Blackboard Compare +The `BeehaveBlackboardCompare` node compares two values using a variety of comparison operators. + +Here's how the node can be used to check if a blackboard key is equal to a certain value: +![Blackboard Compare Example](../assets/blackboard_compare_equal.png) + +Since both operands are expressions, you can also do more advanced comparisons, like checking whether a direction vector exceeds a given length: +![Blackboard Compare Example](../assets/blackboard_compare_advanced.png) + +### Blackboard Has +The `BeehaveBlackboardHas` node returns `SUCCESS` is the blackboard has the specified key and `FAILURE` if it doesn't. \ No newline at end of file diff --git a/examples/actions/SetModulateColor.gd b/examples/actions/SetModulateColor.gd index 311de366..89371685 100644 --- a/examples/actions/SetModulateColor.gd +++ b/examples/actions/SetModulateColor.gd @@ -1,4 +1,4 @@ -extends ActionLeaf +extends BeehaveAction @export var modulate_color:Color = Color.WHITE @export var interpolation_time:float = 3.0 @@ -6,17 +6,17 @@ extends ActionLeaf var current_color var tween -func tick(actor: Node, _blackboard: Blackboard) -> int: +func tick(context: BeehaveContext) -> int: - if current_color != modulate_color and actor.modulate != modulate_color: + if current_color != modulate_color and context.get_actor().modulate != modulate_color: if tween != null: tween.stop() current_color = modulate_color tween = create_tween()\ .set_ease(Tween.EASE_IN_OUT)\ .set_trans(Tween.TRANS_CUBIC) - tween.tween_property(actor, "modulate", current_color, interpolation_time)\ - .finished.connect(_finished.bind(actor)) + tween.tween_property(context.get_actor(), "modulate", current_color, interpolation_time)\ + .finished.connect(_finished.bind(context.get_actor())) if current_color != null: return RUNNING diff --git a/examples/conditions/HasNegativePosition.gd b/examples/conditions/HasNegativePosition.gd index 3eb580e8..f6ad4cf1 100644 --- a/examples/conditions/HasNegativePosition.gd +++ b/examples/conditions/HasNegativePosition.gd @@ -1,7 +1,7 @@ extends ConditionLeaf -func tick(actor: Node, _blackboard: Blackboard) -> int: +func tick(context: BeehaveContext) -> int: if actor.position.x < 0.0 and actor.position.y < 0.0: return SUCCESS else: diff --git a/examples/conditions/HasPositivePosition.gd b/examples/conditions/HasPositivePosition.gd index a9ce844a..2a6421e5 100644 --- a/examples/conditions/HasPositivePosition.gd +++ b/examples/conditions/HasPositivePosition.gd @@ -1,7 +1,7 @@ class_name HasPositivePosition extends ConditionLeaf -func tick(actor: Node, _blackboard: Blackboard) -> int: +func tick(context: BeehaveContext) -> int: if actor.position.x > 0.0 and actor.position.y > 0.0: return SUCCESS else: diff --git a/examples/random_tree_example/RandomAction.gd b/examples/random_tree_example/RandomAction.gd index 9d8049f5..088b2992 100644 --- a/examples/random_tree_example/RandomAction.gd +++ b/examples/random_tree_example/RandomAction.gd @@ -4,7 +4,7 @@ ## [member weights]. Changes its status every [member reset_duration_msec] ## milliseconds. @tool -class_name RandomAction extends ActionLeaf +class_name RandomAction extends BeehaveAction ## How often this action changes its return status, in milliseconds. @@ -31,7 +31,7 @@ func _get_random_action(): return weights.size() - 1 -func tick(actor: Node, blackboard: Blackboard) -> int: +func tick(context: BeehaveContext) -> int: var step = Time.get_ticks_msec() / reset_duration_msec if step != last_step: action = _get_random_action() diff --git a/extension/.gdignore b/extension/.gdignore new file mode 100644 index 00000000..e69de29b diff --git a/extension/src/beehave_context.cpp b/extension/src/beehave_context.cpp new file mode 100644 index 00000000..f9c46e72 --- /dev/null +++ b/extension/src/beehave_context.cpp @@ -0,0 +1,86 @@ +/**************************************************************************/ +/* beehave_context.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "beehave_context.h" +#include "nodes/beehave_blackboard.h" +#include "nodes/beehave_tree.h" + +using namespace godot; + +BeehaveContext::BeehaveContext() { + +} +BeehaveContext ::~BeehaveContext() { + this->blackboard = nullptr; + this->tree = nullptr; + this->actor = nullptr; +} + +void BeehaveContext::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_tree", "tree"), &BeehaveContext::set_tree); + ClassDB::bind_method(D_METHOD("get_tree"), &BeehaveContext::get_tree); + ClassDB::bind_method(D_METHOD("set_blackboard", "blackboard"), &BeehaveContext::set_blackboard); + ClassDB::bind_method(D_METHOD("get_blackboard"), &BeehaveContext::get_blackboard); + ClassDB::bind_method(D_METHOD("set_actor", "actor"), &BeehaveContext::set_actor); + ClassDB::bind_method(D_METHOD("get_actor"), &BeehaveContext::get_actor); + ClassDB::bind_method(D_METHOD("set_delta", "delta"), &BeehaveContext::set_delta); + ClassDB::bind_method(D_METHOD("get_delta"), &BeehaveContext::get_delta); +} + +BeehaveTree *BeehaveContext::get_tree() const { + return this->tree; +} + +void BeehaveContext::set_tree(BeehaveTree *tree) { + this->tree = tree; +} + +BeehaveBlackboard *BeehaveContext::get_blackboard() const { + return this->blackboard; +} + +void BeehaveContext::set_blackboard(BeehaveBlackboard *blackboard) { + this->blackboard = blackboard; +} + +Node *BeehaveContext::get_actor() const { + return this->actor; +} + +void BeehaveContext::set_actor(Node *actor) { + this->actor = actor; +} + +double BeehaveContext::get_delta() const { + return delta; +} + +void BeehaveContext::set_delta(double delta) { + this->delta = delta; +} diff --git a/extension/src/beehave_context.h b/extension/src/beehave_context.h new file mode 100644 index 00000000..d605741a --- /dev/null +++ b/extension/src/beehave_context.h @@ -0,0 +1,71 @@ +/**************************************************************************/ +/* beehave_context.h */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef BEEHAVE_CONTEXT_H +#define BEEHAVE_CONTEXT_H + +#include +#include + +namespace godot { + +// Forward declarations to avoid cyclic dependencies. +class BeehaveTree; +class BeehaveBlackboard; + +class BeehaveContext : public RefCounted { + GDCLASS(BeehaveContext, RefCounted); + + BeehaveTree *tree; + BeehaveBlackboard *blackboard; + Node *actor; + double delta; + +protected: + static void _bind_methods(); + +public: + BeehaveContext(); + ~BeehaveContext(); + BeehaveTree *get_tree() const; + void set_tree(BeehaveTree *tree); + + BeehaveBlackboard *get_blackboard() const; + void set_blackboard(BeehaveBlackboard *blackboard); + + Node *get_actor() const; + void set_actor(Node *actor); + + double get_delta() const; + void set_delta(double delta); +}; + +} //namespace godot + +#endif // BEEHAVE_CONTEXT_H diff --git a/extension/src/nodes/beehave_blackboard.cpp b/extension/src/nodes/beehave_blackboard.cpp new file mode 100644 index 00000000..375fd486 --- /dev/null +++ b/extension/src/nodes/beehave_blackboard.cpp @@ -0,0 +1,83 @@ +/**************************************************************************/ +/* beehave_blackboard.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "beehave_blackboard.h" +#include + +using namespace godot; + +BeehaveBlackboard::BeehaveBlackboard() { +} + +BeehaveBlackboard::~BeehaveBlackboard() { + +} + +void BeehaveBlackboard::_bind_methods() { + // methods + ClassDB::bind_method(D_METHOD("set_value", "key", "value"), &BeehaveBlackboard::set_value); + ClassDB::bind_method(D_METHOD("get_value", "key", "default_value"), &BeehaveBlackboard::get_value); + ClassDB::bind_method(D_METHOD("has_value", "key"), &BeehaveBlackboard::has_value); + ClassDB::bind_method(D_METHOD("erase_value", "key"), &BeehaveBlackboard::erase_value); + ClassDB::bind_method(D_METHOD("get_size"), &BeehaveBlackboard::get_size); + ClassDB::bind_method(D_METHOD("set_values"), &BeehaveBlackboard::set_values); + ClassDB::bind_method(D_METHOD("get_values"), &BeehaveBlackboard::get_values); + + // exports + ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "values"), "set_values", "get_values"); + +} + +Dictionary BeehaveBlackboard::get_values() const { + return values; +} + +void BeehaveBlackboard::set_values(Dictionary values) { + this->values = values; +} + +void BeehaveBlackboard::set_value(const String &key, Variant value) { + this->values[key] = value; +} + +Variant BeehaveBlackboard::get_value(const String &key, Variant default_value) const { + return values.get(key, default_value); +} + +bool BeehaveBlackboard::has_value(const String &key) const { + return values.has(key); +} + +bool BeehaveBlackboard::erase_value(const String &key) { + return values.erase(key); +} + +int BeehaveBlackboard::get_size() const { + return values.size(); +} \ No newline at end of file diff --git a/extension/src/nodes/beehave_blackboard.h b/extension/src/nodes/beehave_blackboard.h new file mode 100644 index 00000000..26bfb035 --- /dev/null +++ b/extension/src/nodes/beehave_blackboard.h @@ -0,0 +1,62 @@ +/**************************************************************************/ +/* beehave_blackboard.h */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef BEEHAVE_BLACKBOARD_H +#define BEEHAVE_BLACKBOARD_H + +#include +#include + +namespace godot { + +class BeehaveBlackboard : public Node { + GDCLASS(BeehaveBlackboard, Node); + + Dictionary values = Dictionary(); + +protected: + static void _bind_methods(); + +public: + BeehaveBlackboard(); + ~BeehaveBlackboard(); + + void set_value(const String &key, Variant value); + Variant get_value(const String &key, Variant default_value) const; + bool has_value(const String &key) const; + bool erase_value(const String &key); + int get_size() const; + + Dictionary get_values() const; + void set_values(Dictionary values); +}; + +} //namespace godot + +#endif // BEEHAVE_BLACKBOARD_H diff --git a/extension/src/nodes/beehave_tree.cpp b/extension/src/nodes/beehave_tree.cpp new file mode 100644 index 00000000..31979ce1 --- /dev/null +++ b/extension/src/nodes/beehave_tree.cpp @@ -0,0 +1,228 @@ +/**************************************************************************/ +/* beehave_tree.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "beehave_tree.h" +#include "beehave_blackboard.h" +#include +#include "variant/utility_functions.hpp" + +using namespace godot; + +BeehaveTree::BeehaveTree() : + context(Ref(memnew(BeehaveContext))), + tick_status(BeehaveTickStatus::PENDING), + tick_rate(1), + blackboard(nullptr), + actor(nullptr) { +} + +BeehaveTree::~BeehaveTree() { + _internal_blackboard = nullptr; + blackboard = nullptr; + actor = nullptr; +} + +void BeehaveTree::_bind_methods() { + // signals + ADD_SIGNAL(MethodInfo("enabled")); + ADD_SIGNAL(MethodInfo("disabled")); + + // enums + BIND_ENUM_CONSTANT(IDLE); + BIND_ENUM_CONSTANT(PHYSICS); + + // methods + ClassDB::bind_method(D_METHOD("tick"), &BeehaveTree::tick); + ClassDB::bind_method(D_METHOD("enable"), &BeehaveTree::enable); + ClassDB::bind_method(D_METHOD("disable"), &BeehaveTree::disable); + ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &BeehaveTree::set_enabled); + ClassDB::bind_method(D_METHOD("is_enabled"), &BeehaveTree::is_enabled); + ClassDB::bind_method(D_METHOD("get_tick_status"), &BeehaveTree::get_tick_status); + ClassDB::bind_method(D_METHOD("set_tick_rate", "tick_rate"), &BeehaveTree::set_tick_rate); + ClassDB::bind_method(D_METHOD("get_tick_rate"), &BeehaveTree::get_tick_rate); + ClassDB::bind_method(D_METHOD("set_process_thread", "thread"), &BeehaveTree::set_process_thread); + ClassDB::bind_method(D_METHOD("get_process_thread"), &BeehaveTree::get_process_thread); + ClassDB::bind_method(D_METHOD("set_actor", "actor"), &BeehaveTree::set_actor); + ClassDB::bind_method(D_METHOD("get_actor"), &BeehaveTree::get_actor); + ClassDB::bind_method(D_METHOD("set_blackboard", "blackboard"), &BeehaveTree::set_blackboard); + ClassDB::bind_method(D_METHOD("get_blackboard"), &BeehaveTree::get_blackboard); + + // exports + ADD_PROPERTY(PropertyInfo(Variant::INT, "process_thread", PROPERTY_HINT_ENUM, "Idle,Physics"), "set_process_thread", "get_process_thread"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "is_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "tick_rate"), "set_tick_rate", "get_tick_rate"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "actor", PROPERTY_HINT_NODE_TYPE, "Node"), "set_actor", "get_actor"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "blackboard", PROPERTY_HINT_NODE_TYPE, "BeehaveBlackboard"), "set_blackboard", "get_blackboard"); +} + +void BeehaveTree::_ready() { + this->_internal_blackboard = memnew(BeehaveBlackboard); + add_child(_internal_blackboard, false, Node::INTERNAL_MODE_FRONT); + + set_physics_process(enabled && process_thread == ProcessThread::PHYSICS); + set_process(enabled && process_thread == ProcessThread::IDLE); + + // Randomize at what frames tick() will happen to avoid stutters + _last_tick = rand() % tick_rate; +} + +void BeehaveTree::_process(double delta) { + if (process_thread == BeehaveTree::ProcessThread::IDLE) { + process_internally(delta); + } +} + +void BeehaveTree::_physics_process(double delta) { + if (process_thread == BeehaveTree::ProcessThread::PHYSICS) { + process_internally(delta); + } +} + +void BeehaveTree::set_actor(Node* actor) { + this->actor = actor; +} + +Node* BeehaveTree::get_actor() const { + return actor; +} + +void BeehaveTree::set_blackboard(BeehaveBlackboard *blackboard) { + this->blackboard = blackboard; + +} + +BeehaveBlackboard *BeehaveTree::get_blackboard() const { + return blackboard; +} + +void BeehaveTree::enable() { + enabled = true; + + set_physics_process(enabled && process_thread == ProcessThread::PHYSICS); + set_process(enabled && process_thread == ProcessThread::IDLE); +} + +void BeehaveTree::disable() { + enabled = false; + + set_physics_process(enabled && process_thread == ProcessThread::PHYSICS); + set_process(enabled && process_thread == ProcessThread::IDLE); + + interrupt(); +} + +void BeehaveTree::set_enabled(bool enabled) { + this->enabled = enabled; + + if(enabled) { + enable(); + } + else { + disable(); + } +} + +bool BeehaveTree::is_enabled() const { + return enabled; +} + +void BeehaveTree::set_tick_rate(int tick_rate) { + this->tick_rate = tick_rate; +} + +int BeehaveTree::get_tick_rate() const { + return tick_rate; +} + +BeehaveTickStatus BeehaveTree::get_tick_status() const { + return tick_status; +} + +void BeehaveTree::set_process_thread(BeehaveTree::ProcessThread thread) { + process_thread = thread; + + set_physics_process(enabled && process_thread == ProcessThread::PHYSICS); + set_process(enabled && process_thread == ProcessThread::IDLE); +} + +BeehaveTree::ProcessThread BeehaveTree::get_process_thread() const { + return process_thread; +} + +void BeehaveTree::process_internally(double delta) { + // ensure that we consider the current tick rate of the tree + if (_last_tick < tick_rate - 1) { + _last_tick += 1; + return; + } + + _last_tick = 0; + + context->set_delta(delta); + + tick(); +} + +BeehaveTickStatus BeehaveTree::tick() { + context->set_tree(this); + context->set_actor(actor ? actor : get_parent()); + context->set_blackboard(blackboard ? blackboard : _internal_blackboard); + + if (get_child_count() == 0) { + tick_status = BeehaveTickStatus::FAILURE; + return tick_status; + } + + for (int i = 0; i < get_child_count(); i++) { + Node *child = get_child(i); + if (!child) { + continue; + } + BeehaveTreeNode *tree_node = cast_to(child); + if (tree_node == nullptr) { + // Skip nodes that aren't valid Beehave nodes + continue; + } + + if (tick_status != BeehaveTickStatus::RUNNING) { + tree_node->before_run(context); + } + + tick_status = tree_node->tick(context); + + if (tick_status != BeehaveTickStatus::RUNNING) { + tree_node->after_run(context); + } + } + return tick_status; +} + +void BeehaveTree::interrupt() { + // TODO: interrupt currently running child, if it exists +} \ No newline at end of file diff --git a/extension/src/nodes/beehave_tree.h b/extension/src/nodes/beehave_tree.h new file mode 100644 index 00000000..d99681e6 --- /dev/null +++ b/extension/src/nodes/beehave_tree.h @@ -0,0 +1,95 @@ +/**************************************************************************/ +/* beehave_tree.h */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef BEEHAVE_TREE_H +#define BEEHAVE_TREE_H + +#include "beehave_blackboard.h" +#include "beehave_context.h" +#include "beehave_tree_node.h" +#include + +namespace godot { + +class BeehaveTree : public Node { + GDCLASS(BeehaveTree, Node); + +public: + enum ProcessThread { + IDLE = 0, + PHYSICS = 1 + }; + +private: + int tick_rate; + bool enabled; + Node *actor; + BeehaveBlackboard *blackboard; + BeehaveBlackboard *_internal_blackboard = nullptr; + Ref context; + BeehaveTickStatus tick_status; + ProcessThread process_thread = ProcessThread::PHYSICS; + + int _last_tick; + + void process_internally(double delta); + +protected: + static void _bind_methods(); + +public: + BeehaveTree(); + ~BeehaveTree(); + + void _ready(); + void _process(double delta); + void _physics_process(double delta); + + void enable(); + void disable(); + void set_enabled(bool enabled); + bool is_enabled() const; + void set_actor(Node *actor); + Node *get_actor() const; + void set_blackboard(BeehaveBlackboard *blackboard); + BeehaveBlackboard *get_blackboard() const; + BeehaveTickStatus tick(); + BeehaveTickStatus get_tick_status() const; + void set_tick_rate(int tick_rate); + int get_tick_rate() const; + void set_process_thread(BeehaveTree::ProcessThread thread); + BeehaveTree::ProcessThread get_process_thread() const; + void interrupt(); +}; + +} //namespace godot + +VARIANT_ENUM_CAST(BeehaveTree::ProcessThread); + +#endif // BEEHAVE_TREE_H diff --git a/extension/src/nodes/beehave_tree_node.cpp b/extension/src/nodes/beehave_tree_node.cpp new file mode 100644 index 00000000..d71a3cec --- /dev/null +++ b/extension/src/nodes/beehave_tree_node.cpp @@ -0,0 +1,79 @@ +/**************************************************************************/ +/* beehave_tree_node.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "beehave_tree_node.h" +#include +#include "variant/utility_functions.hpp" + +using namespace godot; + +BeehaveTreeNode::BeehaveTreeNode() { +} + +BeehaveTreeNode::~BeehaveTreeNode() { +} + +BeehaveTickStatus BeehaveTreeNode::tick(Ref context) { + BeehaveTickStatus status = BeehaveTickStatus::PENDING; + GDVIRTUAL_CALL(_tick, context, status); + return status; +} + +void BeehaveTreeNode::interrupt(Ref context) { + GDVIRTUAL_CALL(_interrupt, context); +} + +void BeehaveTreeNode::before_run(Ref context) { + GDVIRTUAL_CALL(_before_run, context); +} + +void BeehaveTreeNode::after_run(Ref context) { + GDVIRTUAL_CALL(_after_run, context); +} + +BeehaveTreeNode* BeehaveTreeNode::cast_node(Node* node) const { + BeehaveTreeNode *tree_node = cast_to(node); + if (!tree_node) { + return nullptr; + } + return tree_node; +} + +void BeehaveTreeNode::_bind_methods() { + + GDVIRTUAL_BIND(_tick, "context"); + GDVIRTUAL_BIND(_interrupt, "context"); + GDVIRTUAL_BIND(_before_run, "context"); + GDVIRTUAL_BIND(_after_run, "context"); + + BIND_ENUM_CONSTANT(PENDING); + BIND_ENUM_CONSTANT(SUCCESS); + BIND_ENUM_CONSTANT(FAILURE); + BIND_ENUM_CONSTANT(RUNNING); +} \ No newline at end of file diff --git a/extension/src/nodes/beehave_tree_node.h b/extension/src/nodes/beehave_tree_node.h new file mode 100644 index 00000000..a422f3a6 --- /dev/null +++ b/extension/src/nodes/beehave_tree_node.h @@ -0,0 +1,85 @@ +/**************************************************************************/ +/* beehave_tree_node.h */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef BEEHAVE_TREE_NODE_H +#define BEEHAVE_TREE_NODE_H + +#include "beehave_context.h" +#include +#include +#include +#include +#include +#include + +namespace godot { + enum BeehaveTickStatus { + PENDING = -1, + SUCCESS = 0, + FAILURE = 1, + RUNNING = 2 + }; + +} //namespace godot + +VARIANT_ENUM_CAST(BeehaveTickStatus); + +namespace godot { + +class BeehaveTreeNode : public Node { + GDCLASS(BeehaveTreeNode, Node); + +protected: + static void _bind_methods(); + BeehaveTreeNode *cast_node(Node* node) const; + +public: + BeehaveTreeNode(); + ~BeehaveTreeNode(); + + virtual BeehaveTickStatus tick(Ref context); + + GDVIRTUAL1RC(BeehaveTickStatus, _tick, Ref); + + virtual void interrupt(Ref context); + + GDVIRTUAL1C(_interrupt, Ref); + + virtual void before_run(Ref context); + + GDVIRTUAL1C(_before_run, Ref); + + virtual void after_run(Ref context); + + GDVIRTUAL1C(_after_run, Ref); +}; + +} //namespace godot + +#endif // BEEHAVE_TREE_NODE_H diff --git a/extension/src/nodes/composites/beehave_composite.cpp b/extension/src/nodes/composites/beehave_composite.cpp new file mode 100644 index 00000000..8aa56fc8 --- /dev/null +++ b/extension/src/nodes/composites/beehave_composite.cpp @@ -0,0 +1,74 @@ +/**************************************************************************/ +/* beehave_composite.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "beehave_composite.h" + +using namespace godot; + +BeehaveComposite::BeehaveComposite() { + +} + +BeehaveComposite::~BeehaveComposite() { + +} + +void BeehaveComposite::_bind_methods() { + +} + +void BeehaveComposite::after_run(Ref context) { + running_child = nullptr; +} + +void BeehaveComposite::interrupt(Ref context) { + if (running_child) { + running_child->interrupt(context); + running_child = nullptr; + } + BeehaveTreeNode::interrupt(context); +} + +void BeehaveComposite::interrupt_children(Ref context, int from_index, int to_index) { + if (from_index >= to_index) { + return; + } + + TypedArray children = get_children(); + + for (int i = from_index; i < to_index; ++i) { + BeehaveTreeNode *child = cast_node(Object::cast_to(children[i])); + if (child == nullptr) { + // Skip all children which aren't Beehave nodes + continue; + } + + child->interrupt(context); + } +} \ No newline at end of file diff --git a/extension/src/nodes/composites/beehave_composite.h b/extension/src/nodes/composites/beehave_composite.h new file mode 100644 index 00000000..b5ff8fd4 --- /dev/null +++ b/extension/src/nodes/composites/beehave_composite.h @@ -0,0 +1,63 @@ +/**************************************************************************/ +/* beehave_composite.h */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef BEEHAVE_COMPOSITE_H +#define BEEHAVE_COMPOSITE_H + +#include "nodes/beehave_tree_node.h" + +namespace godot { + +class BeehaveComposite : public BeehaveTreeNode { + GDCLASS(BeehaveComposite, BeehaveTreeNode); + +protected: + BeehaveTreeNode *running_child; + +protected: + static void _bind_methods(); + +public: + BeehaveComposite(); + ~BeehaveComposite(); + + void after_run(Ref context); + + void interrupt(Ref context); + +protected: + /* + *Interrupt all children between from_index and to_index (non-inclusive) + *For example, `interrupt_children(context, 2, 6)` will interrupt children at indices 2, 3, 4 and 5, but not 6 + */ + void interrupt_children(Ref context, int from_index, int to_index); +}; +} //namespace godot + +#endif //BEEHAVE_COMPOSITE_H \ No newline at end of file diff --git a/extension/src/nodes/composites/beehave_composite_random.cpp b/extension/src/nodes/composites/beehave_composite_random.cpp new file mode 100644 index 00000000..11f2d57b --- /dev/null +++ b/extension/src/nodes/composites/beehave_composite_random.cpp @@ -0,0 +1,68 @@ +/**************************************************************************/ +/* beehave_composite_random.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "beehave_composite_random.h" +#include +#include + +using namespace godot; + +BeehaveCompositeRandom::BeehaveCompositeRandom(): +random_seed(0) { + +} + +BeehaveCompositeRandom::~BeehaveCompositeRandom() { + +} + +void BeehaveCompositeRandom::set_random_seed(int random_seed) { + this->random_seed = random_seed; + if (random_seed != 0) UtilityFunctions::seed(random_seed); + else UtilityFunctions::randomize(); +} + +int BeehaveCompositeRandom::get_random_seed() const { + return random_seed; +} + +void BeehaveCompositeRandom::_bind_methods() { + // methods + ClassDB::bind_method(D_METHOD("set_random_seed", "random_seed"), &BeehaveCompositeRandom::set_random_seed); + ClassDB::bind_method(D_METHOD("get_random_seed"), &BeehaveCompositeRandom::get_random_seed); + + // exports + ADD_PROPERTY(PropertyInfo(Variant::INT, "random_seed"), "set_random_seed", "get_random_seed"); +} + +TypedArray BeehaveCompositeRandom::get_shuffled_children() { + TypedArray children_bag = get_children().duplicate(); + children_bag.shuffle(); + return children_bag; +} \ No newline at end of file diff --git a/extension/src/nodes/composites/beehave_composite_random.h b/extension/src/nodes/composites/beehave_composite_random.h new file mode 100644 index 00000000..fb11653e --- /dev/null +++ b/extension/src/nodes/composites/beehave_composite_random.h @@ -0,0 +1,61 @@ +/**************************************************************************/ +/* beehave_composite_random.h */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef BEEHAVE_COMPOSITE_RANDOM_H +#define BEEHAVE_COMPOSITE_RANDOM_H + +#include "nodes/composites/beehave_composite.h" +#include + +namespace godot { + +class BeehaveCompositeRandom : public BeehaveComposite { + GDCLASS(BeehaveCompositeRandom, BeehaveComposite); + +protected: + // Sets a predictable seed. + int random_seed; + + // TODO: Add weights back in. + +public: + BeehaveCompositeRandom(); + ~BeehaveCompositeRandom(); + + void set_random_seed(int random_seed); + int get_random_seed() const; + +protected: + static void _bind_methods(); + + TypedArray get_shuffled_children(); +}; +} //namespace godot + +#endif //BEEHAVE_COMPOSITE_RANDOM_H \ No newline at end of file diff --git a/extension/src/nodes/composites/beehave_selector.cpp b/extension/src/nodes/composites/beehave_selector.cpp new file mode 100644 index 00000000..46ba47ba --- /dev/null +++ b/extension/src/nodes/composites/beehave_selector.cpp @@ -0,0 +1,127 @@ +/**************************************************************************/ +/* beehave_selector.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "beehave_selector.h" + +using namespace godot; + +BeehaveSelector::BeehaveSelector() { + +} + +BeehaveSelector::~BeehaveSelector() { + +} + +void BeehaveSelector::_bind_methods() { + +} + +BeehaveTickStatus BeehaveSelector::tick(Ref context) { + TypedArray children = get_children(); + int processed_count = 0; + + for (int i = 0; i < children.size(); ++i) { + if (i < last_execution_index) { + // skip everything that was executed already + ++processed_count; + continue; + } + BeehaveTreeNode *child = cast_node(Object::cast_to(children[i])); + if (child == nullptr) { + // skip anything that is not a valid beehave node + continue; + } + + if (child != running_child) { + child->before_run(context); + } + + BeehaveTickStatus response = child->tick(context); + ++processed_count; + + switch (response) { + case SUCCESS: + if (running_child) { + if (running_child != child) { + running_child->interrupt(context); + } + running_child = nullptr; + } + child->after_run(context); + interrupt_children(context, i + 1, previous_success_or_running_index + 1); + + previous_success_or_running_index = i; + ready_to_interrupt_all = false; + return SUCCESS; + case FAILURE: + running_child = nullptr; + child->after_run(context); + ++last_execution_index; + break; + case RUNNING: + if (child != running_child) { + if (running_child) { + running_child->interrupt(context); + } + running_child = child; + } + interrupt_children(context, i + 1, previous_success_or_running_index + 1); + previous_success_or_running_index = i; + ready_to_interrupt_all = false; + return RUNNING; + } + } + + // FIXME: this doesn't account for children that aren't Beehave nodes. In that case, processed_count will never reach children.size()! + // All children failed + ready_to_interrupt_all = (processed_count == children.size()); + last_execution_index = 0; + return BeehaveTickStatus::FAILURE; +} + +void BeehaveSelector::after_run(Ref context) { + last_execution_index = 0; + BeehaveComposite::after_run(context); +} + +void BeehaveSelector::interrupt(Ref context) { + if (ready_to_interrupt_all) { + interrupt_children(context, 0, get_child_count()); + ready_to_interrupt_all = false; + } + else { + interrupt_children(context, last_execution_index + 1, previous_success_or_running_index + 1); + } + + last_execution_index = 0; + previous_success_or_running_index = -1; + + BeehaveComposite::interrupt(context); +} \ No newline at end of file diff --git a/extension/src/nodes/composites/beehave_selector.h b/extension/src/nodes/composites/beehave_selector.h new file mode 100644 index 00000000..c3e8a841 --- /dev/null +++ b/extension/src/nodes/composites/beehave_selector.h @@ -0,0 +1,59 @@ +/**************************************************************************/ +/* beehave_selector.h */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef BEEHAVE_SELECTOR_H +#define BEEHAVE_SELECTOR_H + +#include "nodes/composites/beehave_composite.h" + +namespace godot { + +class BeehaveSelector : public BeehaveComposite { + GDCLASS(BeehaveSelector, BeehaveComposite); + + int last_execution_index = 0; + int previous_success_or_running_index = -1; + bool ready_to_interrupt_all = false; + +protected: + static void _bind_methods(); + +public: + BeehaveSelector(); + ~BeehaveSelector(); + + BeehaveTickStatus tick(Ref context); + + void after_run(Ref context); + + void interrupt(Ref context); +}; +}// namespace godot + +#endif // BEEHAVE_SELECTOR_H \ No newline at end of file diff --git a/extension/src/nodes/composites/beehave_selector_random.cpp b/extension/src/nodes/composites/beehave_selector_random.cpp new file mode 100644 index 00000000..a5f563a5 --- /dev/null +++ b/extension/src/nodes/composites/beehave_selector_random.cpp @@ -0,0 +1,98 @@ +/**************************************************************************/ +/* beehave_selector_random.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "beehave_selector_random.h" +#include + +using namespace godot; + +BeehaveSelectorRandom::BeehaveSelectorRandom() { + if (random_seed == 0) { + UtilityFunctions::randomize(); + } +} + +BeehaveSelectorRandom::~BeehaveSelectorRandom() { + +} + +void BeehaveSelectorRandom::_bind_methods() { + +} + +BeehaveTickStatus BeehaveSelectorRandom::tick(Ref context) { + if (_children_bag.is_empty()) { + _children_bag = get_shuffled_children(); + } + + // Since we're going to remove children from the array, iterate it in reverse order. + for (int i = _children_bag.size() - 1; i >= 0; --i) { + BeehaveTreeNode *child = cast_node(Object::cast_to(_children_bag[i])); + if (child == nullptr) { + // skip anything that is not a valid beehave node + continue; + } + + if (child != running_child) { + child->before_run(context); + } + + BeehaveTickStatus response = child->tick(context); + + switch (response) { + case SUCCESS: + _children_bag.erase(child); + child->after_run(context); + return SUCCESS; + case FAILURE: + _children_bag.erase(child); + child->after_run(context); + break; + case RUNNING: + if (child != running_child) { + if (running_child) { + running_child->interrupt(context); + } + running_child = child; + } + return RUNNING; + } + } + return BeehaveTickStatus::FAILURE; +} + +void BeehaveSelectorRandom::after_run(Ref context) { + _children_bag = get_shuffled_children(); + BeehaveCompositeRandom::after_run(context); +} + +void BeehaveSelectorRandom::interrupt(Ref context) { + _children_bag = get_shuffled_children(); + BeehaveCompositeRandom::interrupt(context); +} \ No newline at end of file diff --git a/extension/src/nodes/composites/beehave_selector_random.h b/extension/src/nodes/composites/beehave_selector_random.h new file mode 100644 index 00000000..a215a640 --- /dev/null +++ b/extension/src/nodes/composites/beehave_selector_random.h @@ -0,0 +1,59 @@ +/**************************************************************************/ +/* beehave_selector_random.h */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef BEEHAVE_SELECTOR_RANDOM_H +#define BEEHAVE_SELECTOR_RANDOM_H + +#include "nodes/composites/beehave_composite_random.h" +#include + +namespace godot { + +class BeehaveSelectorRandom : public BeehaveCompositeRandom { + GDCLASS(BeehaveSelectorRandom, BeehaveCompositeRandom); + +protected: + static void _bind_methods(); + +public: + BeehaveSelectorRandom(); + ~BeehaveSelectorRandom(); + + BeehaveTickStatus tick(Ref context); + + void after_run(Ref context); + + void interrupt(Ref context); + +private: + TypedArray _children_bag; +}; +}// namespace godot + +#endif // BEEHAVE_SELECTOR_RANDOM_H \ No newline at end of file diff --git a/extension/src/nodes/composites/beehave_selector_reactive.cpp b/extension/src/nodes/composites/beehave_selector_reactive.cpp new file mode 100644 index 00000000..edb6f641 --- /dev/null +++ b/extension/src/nodes/composites/beehave_selector_reactive.cpp @@ -0,0 +1,112 @@ +/**************************************************************************/ +/* beehave_selector_reactive.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "beehave_selector_reactive.h" + +using namespace godot; + +BeehaveSelectorReactive::BeehaveSelectorReactive() { + +} + +BeehaveSelectorReactive::~BeehaveSelectorReactive() { + +} + +void BeehaveSelectorReactive::_bind_methods() { + +} + +BeehaveTickStatus BeehaveSelectorReactive::tick(Ref context) { + TypedArray children = get_children(); + int processed_count = 0; + + for (int i = 0; i < children.size(); ++i) { + BeehaveTreeNode *child = cast_node(Object::cast_to(children[i])); + if (child == nullptr) { + // skip anything that is not a valid beehave node + continue; + } + + if (child != running_child) { + child->before_run(context); + } + + BeehaveTickStatus response = child->tick(context); + ++processed_count; + + switch (response) { + case SUCCESS: + if (running_child) { + if (running_child != child) { + running_child->interrupt(context); + } + running_child = nullptr; + } + child->after_run(context); + + previous_success_or_running_index = i; + ready_to_interrupt_all = false; + return SUCCESS; + case FAILURE: + child->after_run(context); + break; + case RUNNING: + if (child != running_child) { + if (running_child) { + running_child->interrupt(context); + } + running_child = child; + } + interrupt_children(context, i + 1, previous_success_or_running_index + 1); + previous_success_or_running_index = i; + ready_to_interrupt_all = false; + return RUNNING; + } + } + + // FIXME: this doesn't account for children that aren't Beehave nodes. In that case, processed_count will never reach children.size()! + // All children failed + ready_to_interrupt_all = (processed_count == children.size()); + return BeehaveTickStatus::FAILURE; +} + +void BeehaveSelectorReactive::interrupt(Ref context) { + if (ready_to_interrupt_all) { + interrupt_children(context, 0, get_child_count()); + ready_to_interrupt_all = false; + } + else { + interrupt_children(context, 0, previous_success_or_running_index + 1); + } + + previous_success_or_running_index = -1; + + BeehaveComposite::interrupt(context); +} \ No newline at end of file diff --git a/extension/src/nodes/composites/beehave_selector_reactive.h b/extension/src/nodes/composites/beehave_selector_reactive.h new file mode 100644 index 00000000..1c4e1b1c --- /dev/null +++ b/extension/src/nodes/composites/beehave_selector_reactive.h @@ -0,0 +1,56 @@ +/**************************************************************************/ +/* beehave_selector_reactive.h */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef BEEHAVE_SELECTOR_REACTIVE_H +#define BEEHAVE_SELECTOR_REACTIVE_H + +#include "nodes/composites/beehave_composite.h" + +namespace godot { + +class BeehaveSelectorReactive : public BeehaveComposite { + GDCLASS(BeehaveSelectorReactive, BeehaveComposite); + + int previous_success_or_running_index = -1; + bool ready_to_interrupt_all = false; + +protected: + static void _bind_methods(); + +public: + BeehaveSelectorReactive(); + ~BeehaveSelectorReactive(); + + BeehaveTickStatus tick(Ref context); + + void interrupt(Ref context); +}; +}// namespace godot + +#endif // BEEHAVE_SELECTOR_REACTIVE_H \ No newline at end of file diff --git a/extension/src/nodes/composites/beehave_sequence.cpp b/extension/src/nodes/composites/beehave_sequence.cpp new file mode 100644 index 00000000..f23508cf --- /dev/null +++ b/extension/src/nodes/composites/beehave_sequence.cpp @@ -0,0 +1,108 @@ +/**************************************************************************/ +/* beehave_sequence.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "beehave_sequence.h" + +using namespace godot; + +BeehaveSequence::BeehaveSequence() { + +} + +BeehaveSequence::~BeehaveSequence() { + +} + +void BeehaveSequence::_bind_methods() { + +} + +BeehaveTickStatus BeehaveSequence::tick(Ref context) { + TypedArray children = get_children(); + for (int i = 0; i < children.size(); ++i) { + if (i < successful_index) { + continue; + } + BeehaveTreeNode *child = cast_node(Object::cast_to(children[i])); + if (child == nullptr) { + // skip anything that is not a valid beehave node + continue; + } + + if (child != running_child) { + child->before_run(context); + } + + BeehaveTickStatus response = child->tick(context); + + switch(response) { + case SUCCESS: + // Do not interrupt as the child finishes running! + running_child = nullptr; + ++successful_index; + break; + case FAILURE: + if (running_child) { + running_child->interrupt(context); + running_child = nullptr; + } + interrupt_children(context, i + 1, previous_success_or_running_index + 1); + + // Remember where we failed for next tick + previous_success_or_running_index = i; + successful_index = 0; + + // Interrupt any child that was RUNNING before, but do not reset! + if (running_child) { + running_child->interrupt(context); + running_child = nullptr; + } + child->after_run(context); + + return FAILURE; + case RUNNING: + if (running_child && child != running_child) { + running_child->interrupt(context); + running_child = nullptr; + } + running_child = child; + interrupt_children(context, i + 1, previous_success_or_running_index + 1); + previous_success_or_running_index = i; + return RUNNING; + } + } + + successful_index = 0; + return BeehaveTickStatus::SUCCESS; +} + +void BeehaveSequence::interrupt(Ref context) { + interrupt_children(context, successful_index, previous_success_or_running_index + 1); + BeehaveComposite::interrupt(context); +} \ No newline at end of file diff --git a/extension/src/nodes/composites/beehave_sequence.h b/extension/src/nodes/composites/beehave_sequence.h new file mode 100644 index 00000000..92ac0611 --- /dev/null +++ b/extension/src/nodes/composites/beehave_sequence.h @@ -0,0 +1,57 @@ +/**************************************************************************/ +/* beehave_sequence.h */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef BEEHAVE_SEQUENCE_H +#define BEEHAVE_SEQUENCE_H + +#include "nodes/composites/beehave_composite.h" + +namespace godot +{ + +class BeehaveSequence : public BeehaveComposite { + GDCLASS(BeehaveSequence, BeehaveComposite); + + int successful_index = 0; + int previous_success_or_running_index = -1; + +protected: + static void _bind_methods(); + +public: + BeehaveSequence(); + ~BeehaveSequence(); + + BeehaveTickStatus tick(Ref context); + + void interrupt(Ref context); +}; +} // namespace godot + +#endif // BEEHAVE_SEQUENCE_H \ No newline at end of file diff --git a/extension/src/nodes/composites/beehave_sequence_random.cpp b/extension/src/nodes/composites/beehave_sequence_random.cpp new file mode 100644 index 00000000..6fad6cbe --- /dev/null +++ b/extension/src/nodes/composites/beehave_sequence_random.cpp @@ -0,0 +1,123 @@ +/**************************************************************************/ +/* beehave_sequence_random.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "beehave_sequence_random.h" +#include + +using namespace godot; + +BeehaveSequenceRandom::BeehaveSequenceRandom() { + if (random_seed == 0) { + UtilityFunctions::randomize(); + } +} + +BeehaveSequenceRandom::~BeehaveSequenceRandom() { + +} + +void BeehaveSequenceRandom::set_resume_on_failure(bool resume_on_failure) { + this->resume_on_failure = resume_on_failure; +} + +bool BeehaveSequenceRandom::get_resume_on_failure() const { + return resume_on_failure; +} + +void BeehaveSequenceRandom::set_resume_on_interrupt(bool resume_on_interrupt) { + this->resume_on_interrupt = resume_on_interrupt; +} + +bool BeehaveSequenceRandom::get_resume_on_interrupt() const { + return resume_on_interrupt; +} + +void BeehaveSequenceRandom::_bind_methods() { + // methods + ClassDB::bind_method(D_METHOD("set_resume_on_failure", "resume_on_failure"), &BeehaveSequenceRandom::set_resume_on_failure); + ClassDB::bind_method(D_METHOD("get_resume_on_failure"), &BeehaveSequenceRandom::get_resume_on_failure); + ClassDB::bind_method(D_METHOD("set_resume_on_interrupt", "resume_on_interrupt"), &BeehaveSequenceRandom::set_resume_on_interrupt); + ClassDB::bind_method(D_METHOD("get_resume_on_interrupt"), &BeehaveSequenceRandom::get_resume_on_interrupt); + + // exports + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "resume_on_failure"), "set_resume_on_failure", "get_resume_on_failure"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "resume_on_interrupt"), "set_resume_on_interrupt", "get_resume_on_interrupt"); +} + +BeehaveTickStatus BeehaveSequenceRandom::tick(Ref context) { + if (_children_bag.is_empty()) { + _children_bag = get_shuffled_children(); + } + + // Since we're going to remove children from the array, iterate it in reverse order. + for (int i = _children_bag.size() -1; i >= 0; --i) { + BeehaveTreeNode *child = cast_node(Object::cast_to(_children_bag[i])); + if (child == nullptr) { + // skip anything that is not a valid beehave node + continue; + } + + if (child != running_child) { + child->before_run(context); + } + + BeehaveTickStatus response = child->tick(context); + + switch(response) { + case SUCCESS: + _children_bag.erase(child); + child->after_run(context); + break; + case FAILURE: + _children_bag.erase(child); + // Interrupt any child that was RUNNING before, but do not reset! + BeehaveCompositeRandom::interrupt(context); + child->after_run(context); + return FAILURE; + case RUNNING: + running_child = child; + return RUNNING; + } + } + return BeehaveTickStatus::SUCCESS; +} + +void BeehaveSequenceRandom::after_run(Ref context) { + if (!resume_on_failure) { + _children_bag = get_shuffled_children(); + } + BeehaveCompositeRandom::after_run(context); +} + +void BeehaveSequenceRandom::interrupt(Ref context) { + if (!resume_on_interrupt) { + _children_bag = get_shuffled_children(); + } + BeehaveCompositeRandom::interrupt(context); +} \ No newline at end of file diff --git a/extension/src/nodes/composites/beehave_sequence_random.h b/extension/src/nodes/composites/beehave_sequence_random.h new file mode 100644 index 00000000..6dee5043 --- /dev/null +++ b/extension/src/nodes/composites/beehave_sequence_random.h @@ -0,0 +1,71 @@ +/**************************************************************************/ +/* beehave_sequence_random.h */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef BEEHAVE_SEQUENCE_RANDOM_H +#define BEEHAVE_SEQUENCE_RANDOM_H + +#include "nodes/composites/beehave_composite_random.h" +#include + +namespace godot +{ + +class BeehaveSequenceRandom : public BeehaveCompositeRandom { + GDCLASS(BeehaveSequenceRandom, BeehaveCompositeRandom); + + // Whether the sequence should start where it left off after a previous failure. + bool resume_on_failure = false; + // Whether the sequence should start where it left off after a previous interruption. + bool resume_on_interrupt = false; + +protected: + static void _bind_methods(); + +public: + BeehaveSequenceRandom(); + ~BeehaveSequenceRandom(); + + void set_resume_on_failure(bool resume_on_failure); + bool get_resume_on_failure() const; + + void set_resume_on_interrupt(bool resume_on_interrupt); + bool get_resume_on_interrupt() const; + + BeehaveTickStatus tick(Ref context); + + void after_run(Ref context); + + void interrupt(Ref context); + +private: + TypedArray _children_bag; +}; +} // namespace godot + +#endif // BEEHAVE_SEQUENCE_RANDOM_H \ No newline at end of file diff --git a/extension/src/nodes/composites/beehave_sequence_reactive.cpp b/extension/src/nodes/composites/beehave_sequence_reactive.cpp new file mode 100644 index 00000000..a8cdd875 --- /dev/null +++ b/extension/src/nodes/composites/beehave_sequence_reactive.cpp @@ -0,0 +1,105 @@ +/**************************************************************************/ +/* beehave_sequence_reactive.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "beehave_sequence_reactive.h" + +using namespace godot; + +BeehaveSequenceReactive::BeehaveSequenceReactive() { + +} + +BeehaveSequenceReactive::~BeehaveSequenceReactive() { + +} + +void BeehaveSequenceReactive::_bind_methods() { + +} + +BeehaveTickStatus BeehaveSequenceReactive::tick(Ref context) { + TypedArray children = get_children(); + for (int i = 0; i < children.size(); ++i) { + BeehaveTreeNode *child = cast_node(Object::cast_to(children[i])); + if (child == nullptr) { + // skip anything that is not a valid beehave node + continue; + } + + if (child != running_child) { + child->before_run(context); + } + + BeehaveTickStatus response = child->tick(context); + + switch(response) { + case SUCCESS: + if (running_child && running_child == child) { + // Do not interrupt this child as it finishes running! + running_child = nullptr; + } + child->after_run(context); + break; + case FAILURE: + interrupt_children(context, i + 1, previous_failure_index + 1); + + previous_failure_index = i; + + if (running_child) { + running_child->interrupt(context); + running_child = nullptr; + } + child->after_run(context); + return FAILURE; + case RUNNING: + previous_failure_index = -1; + previous_running_index = -1; + + if (running_child && running_child != child) { + running_child->interrupt(context); + running_child = nullptr; + } + running_child = child; + interrupt_children(context, i + 1, previous_running_index + 1); + previous_running_index = i; + return RUNNING; + } + } + return BeehaveTickStatus::SUCCESS; +} + +void BeehaveSequenceReactive::interrupt(Ref context) { + int to_index = previous_running_index > previous_failure_index ? previous_running_index : previous_failure_index; + interrupt_children(context, 0, to_index); + + previous_running_index = -1; + previous_failure_index = -1; + + BeehaveComposite::interrupt(context); +} \ No newline at end of file diff --git a/extension/src/nodes/composites/beehave_sequence_reactive.h b/extension/src/nodes/composites/beehave_sequence_reactive.h new file mode 100644 index 00000000..41731ec6 --- /dev/null +++ b/extension/src/nodes/composites/beehave_sequence_reactive.h @@ -0,0 +1,60 @@ +/**************************************************************************/ +/* beehave_sequence_reactive.h */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef BEEHAVE_SEQUENCE_REACTIVE_H +#define BEEHAVE_SEQUENCE_REACTIVE_H + +#include "nodes/composites/beehave_composite.h" + +namespace godot +{ + +class BeehaveSequenceReactive : public BeehaveComposite { + GDCLASS(BeehaveSequenceReactive, BeehaveComposite); + + // Track where we last failed – so we detect a backward jump + int previous_failure_index = -1; + + // Separate index for running as failure and running can diverge in reactive sequence + int previous_running_index = -1; + +protected: + static void _bind_methods(); + +public: + BeehaveSequenceReactive(); + ~BeehaveSequenceReactive(); + + BeehaveTickStatus tick(Ref context); + + void interrupt(Ref context); +}; +} // namespace godot + +#endif // BEEHAVE_SEQUENCE_REACTIVE_H \ No newline at end of file diff --git a/extension/src/nodes/composites/beehave_sequence_star.cpp b/extension/src/nodes/composites/beehave_sequence_star.cpp new file mode 100644 index 00000000..06aee45c --- /dev/null +++ b/extension/src/nodes/composites/beehave_sequence_star.cpp @@ -0,0 +1,110 @@ +/**************************************************************************/ +/* beehave_sequence_star.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "beehave_sequence_star.h" + +using namespace godot; + +BeehaveSequenceStar::BeehaveSequenceStar() { + +} + +BeehaveSequenceStar::~BeehaveSequenceStar() { + +} + +void BeehaveSequenceStar::_bind_methods() { + +} + +BeehaveTickStatus BeehaveSequenceStar::tick(Ref context) { + TypedArray children = get_children(); + for (int i = 0; i < children.size(); ++i) { + if (i < successful_index) { + continue; + } + BeehaveTreeNode *child = cast_node(Object::cast_to(children[i])); + if (child == nullptr) { + // skip anything that is not a valid beehave node + continue; + } + + if (child != running_child) { + child->before_run(context); + } + + BeehaveTickStatus response = child->tick(context); + + switch(response) { + case SUCCESS: + if (running_child && running_child == child) { + // Do not interrupt as this child finishes running! + running_child = nullptr; + } + ++successful_index; + child->after_run(context); + break; + case FAILURE: + interrupt_children(context, i + 1, previous_failure_or_running_index + 1); + + // Remember where we failed for next tick + previous_failure_or_running_index = i; + + // Interrupt any child that was RUNNING before, but do not reset! + if (running_child) { + running_child->interrupt(context); + running_child = nullptr; + } + child->after_run(context); + return FAILURE; + case RUNNING: + if (running_child && running_child != child) { + running_child->interrupt(context); + running_child = nullptr; + } + running_child = child; + + interrupt_children(context, i + 1, previous_failure_or_running_index + 1); + previous_failure_or_running_index = i; + return RUNNING; + } + } + + successful_index = 0; + return BeehaveTickStatus::SUCCESS; +} + +void BeehaveSequenceStar::interrupt(Ref context) { + interrupt_children(context, successful_index, previous_failure_or_running_index + 1); + + successful_index = 0; + previous_failure_or_running_index = -1; + + BeehaveComposite::interrupt(context); +} \ No newline at end of file diff --git a/extension/src/nodes/composites/beehave_sequence_star.h b/extension/src/nodes/composites/beehave_sequence_star.h new file mode 100644 index 00000000..165c226b --- /dev/null +++ b/extension/src/nodes/composites/beehave_sequence_star.h @@ -0,0 +1,58 @@ +/**************************************************************************/ +/* beehave_sequence_star.h */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef BEEHAVE_SEQUENCE_STAR_H +#define BEEHAVE_SEQUENCE_STAR_H + +#include "nodes/composites/beehave_composite.h" + +namespace godot +{ + +class BeehaveSequenceStar : public BeehaveComposite { + GDCLASS(BeehaveSequenceStar, BeehaveComposite); + + int successful_index = 0; + // Track where we last failed – so we detect a backward jump + int previous_failure_or_running_index = -1; + +protected: + static void _bind_methods(); + +public: + BeehaveSequenceStar(); + ~BeehaveSequenceStar(); + + BeehaveTickStatus tick(Ref context); + + void interrupt(Ref context); +}; +} // namespace godot + +#endif // BEEHAVE_SEQUENCE_STAR_H \ No newline at end of file diff --git a/extension/src/nodes/composites/beehave_simple_parallel.cpp b/extension/src/nodes/composites/beehave_simple_parallel.cpp new file mode 100644 index 00000000..b4a1e802 --- /dev/null +++ b/extension/src/nodes/composites/beehave_simple_parallel.cpp @@ -0,0 +1,184 @@ +/**************************************************************************/ +/* beehave_simple_parallel.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "beehave_simple_parallel.h" + +using namespace godot; + +BeehaveSimpleParallel::BeehaveSimpleParallel() { + +} + +BeehaveSimpleParallel::~BeehaveSimpleParallel() { + +} + +void BeehaveSimpleParallel::set_wait_for_secondary_node(bool wait) { + wait_for_secondary_node = wait; +} + +bool BeehaveSimpleParallel::get_wait_for_secondary_node() const { + return wait_for_secondary_node; +} + +void BeehaveSimpleParallel::set_secondary_node_repeat_count(int repeat_count) { + secondary_node_repeat_count = repeat_count; +} + +int BeehaveSimpleParallel::get_secondary_node_repeat_count() const { + return secondary_node_repeat_count; +} + +void BeehaveSimpleParallel::_bind_methods() { + // methods + ClassDB::bind_method(D_METHOD("set_wait_for_secondary_node", "wait"), &BeehaveSimpleParallel::set_wait_for_secondary_node); + ClassDB::bind_method(D_METHOD("get_wait_for_secondary_node"), &BeehaveSimpleParallel::get_wait_for_secondary_node); + ClassDB::bind_method(D_METHOD("set_secondary_node_repeat_count", "repeat_count"), &BeehaveSimpleParallel::set_secondary_node_repeat_count); + ClassDB::bind_method(D_METHOD("get_secondary_node_repeat_count"), &BeehaveSimpleParallel::get_secondary_node_repeat_count); + + // exports + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "wait_for_secondary_node"), "set_wait_for_secondary_node", "get_wait_for_secondary_node"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "secondary_node_repeat_count"), "set_secondary_node_repeat_count", "get_secondary_node_repeat_count"); +} + +BeehaveTickStatus BeehaveSimpleParallel::tick(Ref context) { + bool main_node_ticked = false; + bool secondary_node_ticked = false; + TypedArray children = get_children(); + + for (int i = 0; i < children.size(); ++i) { + BeehaveTreeNode *child = cast_node(Object::cast_to(children[i])); + if (child == nullptr) { + // skip anything that is not a valid beehave node + continue; + } + + // First valid node is considered "main" node. + if (!main_node_ticked && !main_task_finished) { + main_node_ticked = true; + + if (child != running_child) { + child->before_run(context); + } + + BeehaveTickStatus main_response = child->tick(context); + delayed_result = main_response; + + switch(main_response) { + case SUCCESS: + case FAILURE: + running_child = nullptr; + main_task_finished = true; + child->after_run(context); + + if (!wait_for_secondary_node) { + if (secondary_node_running) { + // FIXME: assumes secondary node comes right after main node! + BeehaveTreeNode *secondary = cast_node(Object::cast_to(children[i + 1])); + if (secondary) { + secondary->interrupt(context); + } + } + _reset(); + return delayed_result; + } + case RUNNING: + running_child = child; + break; + } + } + // Next valid node is considered secondary node. + else if (!secondary_node_ticked) { + secondary_node_ticked = true; + + if (secondary_node_repeat_count == 0 || secondary_node_repeat_left > 0) { + if (!secondary_node_running) { + child->before_run(context); + } + + BeehaveTickStatus subtree_response = child->tick(context); + + if(subtree_response != BeehaveTickStatus::RUNNING) { + secondary_node_running = false; + child->after_run(context); + + if (wait_for_secondary_node && main_task_finished) { + _reset(); + return delayed_result; + } + else if (secondary_node_repeat_left > 0) { + --secondary_node_repeat_left; + } + } + else { + secondary_node_running = true; + } + } + } + } + return BeehaveTickStatus::RUNNING; +} + +void BeehaveSimpleParallel::before_run(Ref context) { + secondary_node_repeat_left = secondary_node_repeat_count; + BeehaveComposite::before_run(context); +} + +void BeehaveSimpleParallel::after_run(Ref context) { + _reset(); + BeehaveComposite::after_run(context); +} + +void BeehaveSimpleParallel::interrupt(Ref context) { + TypedArray children = get_children(); + + if (!main_task_finished) { + // FIXME: assumes index 0 is main node + BeehaveTreeNode *main = cast_node(Object::cast_to(children[0])); + if (main) { + main->interrupt(context); + } + } + if (secondary_node_running) { + // FIXME: assumes index 1 is main secondary + BeehaveTreeNode *secondary = cast_node(Object::cast_to(children[1])); + if (secondary) { + secondary->interrupt(context); + } + } + + _reset(); + + BeehaveComposite::interrupt(context); +} + +void BeehaveSimpleParallel::_reset() { + main_task_finished = false; + secondary_node_running = false; +} \ No newline at end of file diff --git a/extension/src/nodes/composites/beehave_simple_parallel.h b/extension/src/nodes/composites/beehave_simple_parallel.h new file mode 100644 index 00000000..13c8a078 --- /dev/null +++ b/extension/src/nodes/composites/beehave_simple_parallel.h @@ -0,0 +1,77 @@ +/**************************************************************************/ +/* beehave_simple_parallel.h */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef BEEHAVE_SIMPLE_PARALLEL_H +#define BEEHAVE_SIMPLE_PARALLEL_H + +#include "nodes/composites/beehave_composite.h" + +namespace godot +{ + +class BeehaveSimpleParallel : public BeehaveComposite { + GDCLASS(BeehaveSimpleParallel, BeehaveComposite); + + // How many times should secondary node repeat, zero means loop forever + int secondary_node_repeat_count = 0; + + // Whether to wait for the secondary node to finish after the primary node has finished. + bool wait_for_secondary_node = false; + + BeehaveTickStatus delayed_result = BeehaveTickStatus::SUCCESS; + bool main_task_finished = false; + bool secondary_node_running = false; + int secondary_node_repeat_left = 0; + +protected: + static void _bind_methods(); + +public: + BeehaveSimpleParallel(); + ~BeehaveSimpleParallel(); + + void set_wait_for_secondary_node(bool wait); + bool get_wait_for_secondary_node() const; + + void set_secondary_node_repeat_count(int repeat_count); + int get_secondary_node_repeat_count() const; + + BeehaveTickStatus tick(Ref context); + + void before_run(Ref context); + + void after_run(Ref context); + + void interrupt(Ref context); + + void _reset(); +}; +} // namespace godot + +#endif // BEEHAVE_SIMPLE_PARALLEL_H \ No newline at end of file diff --git a/extension/src/nodes/decorators/beehave_cooldown.cpp b/extension/src/nodes/decorators/beehave_cooldown.cpp new file mode 100644 index 00000000..8435b63d --- /dev/null +++ b/extension/src/nodes/decorators/beehave_cooldown.cpp @@ -0,0 +1,103 @@ +/**************************************************************************/ +/* beehave_cooldown.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "beehave_cooldown.h" +#include "variant/utility_functions.hpp" + +using namespace godot; + +BeehaveCooldown::BeehaveCooldown(): +wait_time(1), +passed_time(0) { + +} + +BeehaveCooldown::~BeehaveCooldown() { +} + +void BeehaveCooldown::_bind_methods() { + // methods + ClassDB::bind_method(D_METHOD("set_wait_time", "wait_time"), &BeehaveCooldown::set_wait_time); + ClassDB::bind_method(D_METHOD("get_wait_time"), &BeehaveCooldown::get_wait_time); + + // exports + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "wait_time"), "set_wait_time", "get_wait_time"); +} + +void BeehaveCooldown::set_wait_time(float wait_time) { + this->wait_time = wait_time; + passed_time = 0; +} + +float BeehaveCooldown::get_wait_time() const { + return wait_time; +} + +BeehaveTickStatus BeehaveCooldown::tick(Ref context) { + BeehaveTreeNode *tree_node = get_wrapped_child(); + if (!tree_node) { + return BeehaveTickStatus::FAILURE; + } + + BeehaveTickStatus status = BeehaveTickStatus::FAILURE; + + if (tree_node != running_child) { + tree_node->before_run(context); + } + + if (passed_time == 0) { + status = tree_node->tick(context); + UtilityFunctions::print(vformat("cooldown72 status=%s", status)); + } + + passed_time += context->get_delta(); + // the wait time has been reached, time to reset + if (passed_time >= wait_time) { + status = tree_node->tick(context); + UtilityFunctions::print(vformat("cooldown79 status=%s", status)); + // avoid time drift by carrying over miliseconds from previous iteration. + passed_time -= wait_time; + } + UtilityFunctions::print(vformat("cooldown83 status=%s", status)); + + if (status == BeehaveTickStatus::RUNNING) { + running_child = tree_node; + } + else { + tree_node->after_run(context); + } + + return status; +} + +void BeehaveCooldown::interrupt(Ref context) { + // Reset the cooldown when the branch changes + passed_time = 0; + BeehaveDecorator::interrupt(context); +} \ No newline at end of file diff --git a/extension/src/nodes/decorators/beehave_cooldown.h b/extension/src/nodes/decorators/beehave_cooldown.h new file mode 100644 index 00000000..59bfded2 --- /dev/null +++ b/extension/src/nodes/decorators/beehave_cooldown.h @@ -0,0 +1,60 @@ +/**************************************************************************/ +/* beehave_cooldown.h */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef BEEHAVE_COOLDOWN_H +#define BEEHAVE_COOLDOWN_H + +#include "nodes/decorators/beehave_decorator.h" + +namespace godot { + +class BeehaveCooldown : public BeehaveDecorator { + GDCLASS(BeehaveCooldown, BeehaveDecorator); + + float wait_time; + double passed_time; + +protected: + static void _bind_methods(); + +public: + BeehaveCooldown(); + ~BeehaveCooldown(); + + void set_wait_time(float wait_time); + float get_wait_time() const; + + BeehaveTickStatus tick(Ref context); + + void interrupt(Ref context); +}; + +} + +#endif//BEEHAVE_COOLDOWN_H \ No newline at end of file diff --git a/extension/src/nodes/decorators/beehave_decorator.cpp b/extension/src/nodes/decorators/beehave_decorator.cpp new file mode 100644 index 00000000..024c3a84 --- /dev/null +++ b/extension/src/nodes/decorators/beehave_decorator.cpp @@ -0,0 +1,63 @@ +/**************************************************************************/ +/* beehave_decorator.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "beehave_decorator.h" + +using namespace godot; + +BeehaveDecorator::BeehaveDecorator() { + +} + +BeehaveDecorator::~BeehaveDecorator() { + +} + +void BeehaveDecorator::_bind_methods() { + +} + +void BeehaveDecorator::after_run(Ref context) { + running_child = nullptr; +} + +void BeehaveDecorator::interrupt(Ref context) { + if (running_child) { + running_child->interrupt(context); + running_child = nullptr; + } + BeehaveTreeNode::interrupt(context); +} + +BeehaveTreeNode* BeehaveDecorator::get_wrapped_child() const { + if (get_child_count() != 1) { + return nullptr; + } + return cast_node(get_child(0)); +} \ No newline at end of file diff --git a/extension/src/nodes/decorators/beehave_decorator.h b/extension/src/nodes/decorators/beehave_decorator.h new file mode 100644 index 00000000..1a207c74 --- /dev/null +++ b/extension/src/nodes/decorators/beehave_decorator.h @@ -0,0 +1,58 @@ +/**************************************************************************/ +/* beehave_decorator.h */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef BEEHAVE_DECORATOR_H +#define BEEHAVE_DECORATOR_H + +#include "nodes/beehave_tree_node.h" + +namespace godot { + +class BeehaveDecorator : public BeehaveTreeNode { + GDCLASS(BeehaveDecorator, BeehaveTreeNode); + +protected: + BeehaveTreeNode *running_child; + + static void _bind_methods(); + + BeehaveTreeNode *get_wrapped_child() const; + +public: + BeehaveDecorator(); + ~BeehaveDecorator(); + + void after_run(Ref context); + + void interrupt(Ref context); +}; + +} + +#endif//BEEHAVE_DECORATOR_H \ No newline at end of file diff --git a/extension/src/nodes/decorators/beehave_delayer.cpp b/extension/src/nodes/decorators/beehave_delayer.cpp new file mode 100644 index 00000000..bd6dd470 --- /dev/null +++ b/extension/src/nodes/decorators/beehave_delayer.cpp @@ -0,0 +1,98 @@ +/**************************************************************************/ +/* beehave_delayer.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "beehave_delayer.h" + +using namespace godot; + +BeehaveDelayer::BeehaveDelayer(): +wait_time(1), +passed_time(0) { + +} + +BeehaveDelayer::~BeehaveDelayer() { + +} + +void BeehaveDelayer::_bind_methods() { + // methods + ClassDB::bind_method(D_METHOD("set_wait_time", "wait_time"), &BeehaveDelayer::set_wait_time); + ClassDB::bind_method(D_METHOD("get_wait_time"), &BeehaveDelayer::get_wait_time); + + // exports + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "wait_time"), "set_wait_time", "get_wait_time"); +} + + +void BeehaveDelayer::set_wait_time(float wait_time) { + this->wait_time = wait_time; + passed_time = 0; +} + +float BeehaveDelayer::get_wait_time() const { + return wait_time; +} + +BeehaveTickStatus BeehaveDelayer::tick(Ref context) { + BeehaveTreeNode *tree_node = get_wrapped_child(); + if (!tree_node) { + return BeehaveTickStatus::FAILURE; + } + + if (tree_node != running_child) { + tree_node->before_run(context); + } + + passed_time += context->get_delta(); + + // the wait time has been reached, time to reset + if (passed_time >= wait_time) { + // avoid time drift by carrying over miliseconds from previous iteration. + passed_time -= wait_time; + + BeehaveTickStatus status = tree_node->tick(context); + if (status == BeehaveTickStatus::RUNNING) { + running_child = tree_node; + } + else { + tree_node->after_run(context); + passed_time = 0; + } + return status; + } + + return BeehaveTickStatus::RUNNING; +} + +void BeehaveDelayer::interrupt(Ref context) { + // Reset the delay timer when the branch changes + passed_time = 0; + BeehaveDecorator::interrupt(context); +} \ No newline at end of file diff --git a/extension/src/nodes/decorators/beehave_delayer.h b/extension/src/nodes/decorators/beehave_delayer.h new file mode 100644 index 00000000..3d596caf --- /dev/null +++ b/extension/src/nodes/decorators/beehave_delayer.h @@ -0,0 +1,60 @@ +/**************************************************************************/ +/* beehave_delayer.h */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef BEEHAVE_DELAYER_H +#define BEEHAVE_DELAYER_H + +#include "nodes/decorators/beehave_decorator.h" + +namespace godot { + +class BeehaveDelayer : public BeehaveDecorator { + GDCLASS(BeehaveDelayer, BeehaveDecorator); + + float wait_time = 0.0f; + double passed_time; + +protected: + static void _bind_methods(); + +public: + BeehaveDelayer(); + ~BeehaveDelayer(); + + void set_wait_time(float wait_time); + float get_wait_time() const; + + BeehaveTickStatus tick(Ref context); + + void interrupt(Ref context); +}; + +} + +#endif//BEEHAVE_DELAYER_H \ No newline at end of file diff --git a/extension/src/nodes/decorators/beehave_failer.cpp b/extension/src/nodes/decorators/beehave_failer.cpp new file mode 100644 index 00000000..42935bdd --- /dev/null +++ b/extension/src/nodes/decorators/beehave_failer.cpp @@ -0,0 +1,66 @@ +/**************************************************************************/ +/* beehave_failer.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "beehave_failer.h" + +using namespace godot; + +BeehaveFailer::BeehaveFailer() { + +} + +BeehaveFailer::~BeehaveFailer() { + +} + +void BeehaveFailer::_bind_methods() { + +} + +BeehaveTickStatus BeehaveFailer::tick(Ref context) { + BeehaveTreeNode *tree_node = get_wrapped_child(); + if (!tree_node) { + return BeehaveTickStatus::FAILURE; + } + + if (tree_node != running_child) { + tree_node->before_run(context); + } + + BeehaveTickStatus status = tree_node->tick(context); + + if(status == BeehaveTickStatus::RUNNING) { + running_child = tree_node; + return BeehaveTickStatus::RUNNING; + } + else { + tree_node->after_run(context); + return BeehaveTickStatus::FAILURE; + } +} \ No newline at end of file diff --git a/extension/src/nodes/decorators/beehave_failer.h b/extension/src/nodes/decorators/beehave_failer.h new file mode 100644 index 00000000..3a39a7e3 --- /dev/null +++ b/extension/src/nodes/decorators/beehave_failer.h @@ -0,0 +1,52 @@ +/**************************************************************************/ +/* beehave_failer.h */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef BEEHAVE_FAILER_H +#define BEEHAVE_FAILER_H + +#include "nodes/decorators/beehave_decorator.h" + +namespace godot { + +class BeehaveFailer : public BeehaveDecorator { + GDCLASS(BeehaveFailer, BeehaveDecorator); + +protected: + static void _bind_methods(); + +public: + BeehaveFailer(); + ~BeehaveFailer(); + + BeehaveTickStatus tick(Ref context); +}; + +} + +#endif//BEEHAVE_FAILER_H \ No newline at end of file diff --git a/extension/src/nodes/decorators/beehave_inverter.cpp b/extension/src/nodes/decorators/beehave_inverter.cpp new file mode 100644 index 00000000..310a689c --- /dev/null +++ b/extension/src/nodes/decorators/beehave_inverter.cpp @@ -0,0 +1,69 @@ +/**************************************************************************/ +/* beehave_inverter.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "beehave_inverter.h" + +using namespace godot; + +BeehaveInverter::BeehaveInverter() { + +} + +BeehaveInverter::~BeehaveInverter() { + +} + +void BeehaveInverter::_bind_methods() { + +} + +BeehaveTickStatus BeehaveInverter::tick(Ref context) { + BeehaveTreeNode *tree_node = get_wrapped_child(); + if (!tree_node) { + return BeehaveTickStatus::FAILURE; + } + + if (tree_node != running_child) { + tree_node->before_run(context); + } + + BeehaveTickStatus tick_status = tree_node->tick(context); + + if (tick_status == BeehaveTickStatus::FAILURE) { + tree_node->after_run(context); + return BeehaveTickStatus::SUCCESS; + } else if (tick_status == BeehaveTickStatus::SUCCESS) { + tree_node->after_run(context); + return BeehaveTickStatus::FAILURE; + } else if (tick_status == BeehaveTickStatus::RUNNING) { + running_child = tree_node; + } + + return tick_status; +} \ No newline at end of file diff --git a/extension/src/nodes/decorators/beehave_inverter.h b/extension/src/nodes/decorators/beehave_inverter.h new file mode 100644 index 00000000..770c4cd3 --- /dev/null +++ b/extension/src/nodes/decorators/beehave_inverter.h @@ -0,0 +1,52 @@ +/**************************************************************************/ +/* beehave_inverter.h */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef BEEHAVE_INVERTER_H +#define BEEHAVE_INVERTER_H + +#include "nodes/decorators/beehave_decorator.h" + +namespace godot { + +class BeehaveInverter : public BeehaveDecorator { + GDCLASS(BeehaveInverter, BeehaveDecorator); + +protected: + static void _bind_methods(); + +public: + BeehaveInverter(); + ~BeehaveInverter(); + + BeehaveTickStatus tick(Ref context); +}; + +} + +#endif//BEEHAVE_INVERTER_H \ No newline at end of file diff --git a/extension/src/nodes/decorators/beehave_limiter.cpp b/extension/src/nodes/decorators/beehave_limiter.cpp new file mode 100644 index 00000000..d845df9d --- /dev/null +++ b/extension/src/nodes/decorators/beehave_limiter.cpp @@ -0,0 +1,102 @@ +/**************************************************************************/ +/* beehave_limiter.h */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "beehave_limiter.h" + +using namespace godot; + +BeehaveLimiter::BeehaveLimiter(): +max_count(1), +current_count(0) { + +} + +BeehaveLimiter::~BeehaveLimiter() { + +} + +void BeehaveLimiter::_bind_methods() { + // methods + ClassDB::bind_method(D_METHOD("set_max_count", "max_count"), &BeehaveLimiter::set_max_count); + ClassDB::bind_method(D_METHOD("get_max_count"), &BeehaveLimiter::get_max_count); + + // exports + ADD_PROPERTY(PropertyInfo(Variant::INT, "max_count"), "set_max_count", "get_max_count"); +} + +void BeehaveLimiter::set_max_count(int max_count) { + this->max_count = max_count; + current_count = 0; +} + +int BeehaveLimiter::get_max_count() const { + return max_count; +} + +BeehaveTickStatus BeehaveLimiter::tick(Ref context) { + BeehaveTreeNode *tree_node = get_wrapped_child(); + if (!tree_node) { + return BeehaveTickStatus::FAILURE; + } + + if (current_count < max_count) { + BeehaveTickStatus tick_status = tree_node->tick(context); + ++current_count; + + if (tick_status == BeehaveTickStatus::RUNNING) { + running_child = tree_node; + } + else { + current_count = 0; + tree_node->after_run(context); + } + + return tick_status; + } + + interrupt(context); + tree_node->after_run(context); + + return BeehaveTickStatus::FAILURE; +} + +void BeehaveLimiter::before_run(Ref context) { + // Initialize the counter to 0 when we first start running + current_count = 0; + + BeehaveTreeNode *tree_node = get_wrapped_child(); + if (tree_node) { + tree_node->before_run(context); + } +} + +void BeehaveLimiter::interrupt(Ref context) { + current_count = 0; + BeehaveDecorator::interrupt(context); +} \ No newline at end of file diff --git a/extension/src/nodes/decorators/beehave_limiter.h b/extension/src/nodes/decorators/beehave_limiter.h new file mode 100644 index 00000000..eb2caad2 --- /dev/null +++ b/extension/src/nodes/decorators/beehave_limiter.h @@ -0,0 +1,62 @@ +/**************************************************************************/ +/* beehave_limiter.h */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef BEEHAVE_LIMITER_H +#define BEEHAVE_LIMITER_H + +#include "nodes/decorators/beehave_decorator.h" + +namespace godot { + +class BeehaveLimiter : public BeehaveDecorator { + GDCLASS(BeehaveLimiter, BeehaveDecorator); + + int max_count; + int current_count; + +protected: + static void _bind_methods(); + +public: + BeehaveLimiter(); + ~BeehaveLimiter(); + + void set_max_count(int max_count); + int get_max_count() const; + + BeehaveTickStatus tick(Ref context); + + void before_run(Ref context); + + void interrupt(Ref context); +}; + +} + +#endif//BEEHAVE_LIMITER_H \ No newline at end of file diff --git a/extension/src/nodes/decorators/beehave_repeater.cpp b/extension/src/nodes/decorators/beehave_repeater.cpp new file mode 100644 index 00000000..dcea67f4 --- /dev/null +++ b/extension/src/nodes/decorators/beehave_repeater.cpp @@ -0,0 +1,106 @@ +/**************************************************************************/ +/* beehave_repeater.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "beehave_repeater.h" + +using namespace godot; + +BeehaveRepeater::BeehaveRepeater(): +repetitions(1), +current_count(0) { + +} + + +BeehaveRepeater::~BeehaveRepeater() { + +} + +void BeehaveRepeater::_bind_methods() { + // methods + ClassDB::bind_method(D_METHOD("set_repetitions", "repetitions"), &BeehaveRepeater::set_repetitions); + ClassDB::bind_method(D_METHOD("get_repetitions"), &BeehaveRepeater::get_repetitions); + + // exports + ADD_PROPERTY(PropertyInfo(Variant::INT, "repetitions"), "set_repetitions", "get_repetitions"); +} + + +void BeehaveRepeater::set_repetitions(int repetitions) { + this->repetitions = repetitions; + current_count = 0; +} + +int BeehaveRepeater::get_repetitions() const { + return repetitions; +} + +BeehaveTickStatus BeehaveRepeater::tick(Ref context) { + BeehaveTreeNode *tree_node = get_wrapped_child(); + if (!tree_node) { + return BeehaveTickStatus::FAILURE; + } + + if (current_count < repetitions) { + if (tree_node != running_child) { + tree_node->before_run(context); + } + + BeehaveTickStatus tick_status = tree_node->tick(context); + + if (tick_status == BeehaveTickStatus::RUNNING) { + running_child = tree_node; + return BeehaveTickStatus::RUNNING; + } + + ++current_count; + running_child = nullptr; + + if (tick_status == BeehaveTickStatus::FAILURE) { + return BeehaveTickStatus::FAILURE; + } + + if (current_count >= repetitions) { + return BeehaveTickStatus::SUCCESS; + } + + return BeehaveTickStatus::RUNNING; + } + + return BeehaveTickStatus::SUCCESS; +} + +void BeehaveRepeater::before_run(Ref context) { + current_count = 0; +} + +void BeehaveRepeater::interrupt(Ref context) { + current_count = 0; + BeehaveDecorator::interrupt(context); +} \ No newline at end of file diff --git a/extension/src/nodes/decorators/beehave_repeater.h b/extension/src/nodes/decorators/beehave_repeater.h new file mode 100644 index 00000000..9847467f --- /dev/null +++ b/extension/src/nodes/decorators/beehave_repeater.h @@ -0,0 +1,62 @@ +/**************************************************************************/ +/* beehave_repeater.h */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef BEEHAVE_REPEATER_H +#define BEEHAVE_REPEATER_H + +#include "nodes/decorators/beehave_decorator.h" + +namespace godot { + +class BeehaveRepeater : public BeehaveDecorator { + GDCLASS(BeehaveRepeater, BeehaveDecorator); + + int repetitions; + int current_count; + +protected: + static void _bind_methods(); + +public: + BeehaveRepeater(); + ~BeehaveRepeater(); + + void set_repetitions(int repetitions); + int get_repetitions() const; + + BeehaveTickStatus tick(Ref context); + + void before_run(Ref context); + + void interrupt(Ref context); +}; + +} + +#endif \ No newline at end of file diff --git a/extension/src/nodes/decorators/beehave_succeeder.cpp b/extension/src/nodes/decorators/beehave_succeeder.cpp new file mode 100644 index 00000000..9da6bc6b --- /dev/null +++ b/extension/src/nodes/decorators/beehave_succeeder.cpp @@ -0,0 +1,66 @@ +/**************************************************************************/ +/* beehave_succeeder.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "beehave_succeeder.h" + +using namespace godot; + +BeehaveSucceeder::BeehaveSucceeder() { + +} + +BeehaveSucceeder::~BeehaveSucceeder() { + +} + +void BeehaveSucceeder::_bind_methods() { + +} + +BeehaveTickStatus BeehaveSucceeder::tick(Ref context) { + BeehaveTreeNode *tree_node = get_wrapped_child(); + if (!tree_node) { + return BeehaveTickStatus::FAILURE; + } + + if (tree_node != running_child) { + tree_node->before_run(context); + } + + BeehaveTickStatus status = tree_node->tick(context); + + if(status == BeehaveTickStatus::RUNNING) { + running_child = tree_node; + return BeehaveTickStatus::RUNNING; + } + else { + tree_node->after_run(context); + return BeehaveTickStatus::SUCCESS; + } +} \ No newline at end of file diff --git a/extension/src/nodes/decorators/beehave_succeeder.h b/extension/src/nodes/decorators/beehave_succeeder.h new file mode 100644 index 00000000..64f3c92d --- /dev/null +++ b/extension/src/nodes/decorators/beehave_succeeder.h @@ -0,0 +1,50 @@ +/**************************************************************************/ +/* beehave_succeeder.h */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef BEEHAVE_SUCCEEDER +#define BEEHAVE_SUCCEEDER + +#include "nodes/decorators/beehave_decorator.h" + +namespace godot { + +class BeehaveSucceeder : public BeehaveDecorator { + GDCLASS(BeehaveSucceeder, BeehaveDecorator); + +protected: + static void _bind_methods(); + +public: + BeehaveSucceeder(); + ~BeehaveSucceeder(); + BeehaveTickStatus tick(Ref context); +}; +} + +#endif //BEEHAVE_SUCCEEDER \ No newline at end of file diff --git a/extension/src/nodes/decorators/beehave_time_limiter.cpp b/extension/src/nodes/decorators/beehave_time_limiter.cpp new file mode 100644 index 00000000..af0b18c4 --- /dev/null +++ b/extension/src/nodes/decorators/beehave_time_limiter.cpp @@ -0,0 +1,100 @@ +/**************************************************************************/ +/* beehave_time_limiter.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "beehave_time_limiter.h" + +using namespace godot; + +BeehaveTimeLimiter::BeehaveTimeLimiter(): + wait_time(1.0), + passed_time(0) { + +} + +BeehaveTimeLimiter::~BeehaveTimeLimiter() { +} + +void BeehaveTimeLimiter::_bind_methods() { + // methods + ClassDB::bind_method(D_METHOD("set_wait_time", "wait_time"), &BeehaveTimeLimiter::set_wait_time); + ClassDB::bind_method(D_METHOD("get_wait_time"), &BeehaveTimeLimiter::get_wait_time); + + // exports + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "wait_time"), "set_wait_time", "get_wait_time"); +} + +void BeehaveTimeLimiter::set_wait_time(float wait_time) { + this->wait_time = wait_time; +} + +float BeehaveTimeLimiter::get_wait_time() const { + return wait_time; +} + +BeehaveTickStatus BeehaveTimeLimiter::tick(Ref context) { + BeehaveTreeNode *tree_node = get_wrapped_child(); + if (!tree_node) { + return BeehaveTickStatus::FAILURE; + } + + // Time has run out + if (passed_time >= wait_time) { + interrupt(context); + tree_node->after_run(context); + return FAILURE; + } + + passed_time += context->get_delta(); + BeehaveTickStatus status = tree_node->tick(context); + + if (status == BeehaveTickStatus::RUNNING) { + running_child = tree_node; + } + else { + tree_node->after_run(context); + } + + return status; +} + +void BeehaveTimeLimiter::before_run(Ref context) { + passed_time = 0; + + BeehaveTreeNode *tree_node = get_wrapped_child(); + if (tree_node) { + tree_node->before_run(context); + } +} + +void BeehaveTimeLimiter::interrupt(Ref context) { + // Reset the timer when the node is interrupted + passed_time = 0; + + BeehaveDecorator::interrupt(context); +} \ No newline at end of file diff --git a/extension/src/nodes/decorators/beehave_time_limiter.h b/extension/src/nodes/decorators/beehave_time_limiter.h new file mode 100644 index 00000000..b33b65f5 --- /dev/null +++ b/extension/src/nodes/decorators/beehave_time_limiter.h @@ -0,0 +1,62 @@ +/**************************************************************************/ +/* beehave_time_limiter.h */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef BEEHAVE_TIME_LIMITER_H +#define BEEHAVE_TIME_LIMITER_H + +#include "nodes/decorators/beehave_decorator.h" + +namespace godot { + +class BeehaveTimeLimiter : public BeehaveDecorator { + GDCLASS(BeehaveTimeLimiter, BeehaveDecorator); + + float wait_time; + double passed_time; + +protected: + static void _bind_methods(); + +public: + BeehaveTimeLimiter(); + ~BeehaveTimeLimiter(); + + void set_wait_time(float wait_time); + float get_wait_time() const; + + BeehaveTickStatus tick(Ref context); + + void before_run(Ref context); + + void interrupt(Ref context); +}; + +} + +#endif \ No newline at end of file diff --git a/extension/src/nodes/decorators/beehave_until_fail.cpp b/extension/src/nodes/decorators/beehave_until_fail.cpp new file mode 100644 index 00000000..03e667f0 --- /dev/null +++ b/extension/src/nodes/decorators/beehave_until_fail.cpp @@ -0,0 +1,69 @@ +/**************************************************************************/ +/* beehave_until_fail.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "beehave_until_fail.h" + +using namespace godot; + +BeehaveUntilFail::BeehaveUntilFail() { + +} + +BeehaveUntilFail::~BeehaveUntilFail() { + +} + +void BeehaveUntilFail::_bind_methods() { + +} + +BeehaveTickStatus BeehaveUntilFail::tick(Ref context) { + BeehaveTreeNode *tree_node = get_wrapped_child(); + if (!tree_node) { + return BeehaveTickStatus::FAILURE; + } + + if (tree_node != running_child) { + tree_node->before_run(context); + } + + BeehaveTickStatus status = tree_node->tick(context); + + switch (status) { + case RUNNING: + running_child = tree_node; + return RUNNING; + case SUCCESS: + tree_node->after_run(context); + return RUNNING; + case FAILURE: + tree_node->after_run(context); + return SUCCESS; + } +} \ No newline at end of file diff --git a/extension/src/nodes/decorators/beehave_until_fail.h b/extension/src/nodes/decorators/beehave_until_fail.h new file mode 100644 index 00000000..fddd3c55 --- /dev/null +++ b/extension/src/nodes/decorators/beehave_until_fail.h @@ -0,0 +1,51 @@ +/**************************************************************************/ +/* beehave_until_fail.h */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef BEEHAVE_UNTIL_FAIL_H +#define BEEHAVE_UNTIL_FAIL_H + +#include "nodes/decorators/beehave_decorator.h" + +namespace godot { + +class BeehaveUntilFail : public BeehaveDecorator { + GDCLASS(BeehaveUntilFail, BeehaveDecorator); + +protected: + static void _bind_methods(); + +public: + BeehaveUntilFail(); + ~BeehaveUntilFail(); + + BeehaveTickStatus tick(Ref context); +}; +} + +#endif \ No newline at end of file diff --git a/extension/src/nodes/leaves/beehave_action.cpp b/extension/src/nodes/leaves/beehave_action.cpp new file mode 100644 index 00000000..04e82f3f --- /dev/null +++ b/extension/src/nodes/leaves/beehave_action.cpp @@ -0,0 +1,42 @@ +/**************************************************************************/ +/* beehave_acti.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "beehave_action.h" + +using namespace godot; + +void BeehaveAction::_bind_methods() { +} + +BeehaveAction::BeehaveAction() { + +} +BeehaveAction::~BeehaveAction() { + +} \ No newline at end of file diff --git a/extension/src/nodes/leaves/beehave_action.h b/extension/src/nodes/leaves/beehave_action.h new file mode 100644 index 00000000..19889efa --- /dev/null +++ b/extension/src/nodes/leaves/beehave_action.h @@ -0,0 +1,48 @@ +/**************************************************************************/ +/* beehave_action.h */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef BEEHAVE_ACTION_H +#define BEEHAVE_ACTION_H + +#include "nodes/leaves/beehave_leaf.h" + +namespace godot { + class BeehaveAction : public BeehaveLeaf { + GDCLASS(BeehaveAction, BeehaveLeaf); + + protected: + static void _bind_methods(); + + public: + BeehaveAction(); + ~BeehaveAction(); + }; +} + +#endif //BEEHAVE_ACTION_H \ No newline at end of file diff --git a/extension/src/nodes/leaves/beehave_blackboard_compare.cpp b/extension/src/nodes/leaves/beehave_blackboard_compare.cpp new file mode 100644 index 00000000..efafa9a9 --- /dev/null +++ b/extension/src/nodes/leaves/beehave_blackboard_compare.cpp @@ -0,0 +1,184 @@ +/**************************************************************************/ +/* beehave_blackboard_compare.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "beehave_blackboard_compare.h" +#include "nodes/beehave_blackboard.h" +#include +#include + +using namespace godot; + +void BeehaveBlackboardCompare::_bind_methods() { + // enums + BIND_ENUM_CONSTANT(EQUAL); + BIND_ENUM_CONSTANT(NOT_EQUAL); + BIND_ENUM_CONSTANT(GREATER); + BIND_ENUM_CONSTANT(LESS); + BIND_ENUM_CONSTANT(GREATER_EQUAL); + BIND_ENUM_CONSTANT(LESS_EQUAL); + + // methods + ClassDB::bind_method(D_METHOD("set_left_operand", "left_operand"), &BeehaveBlackboardCompare::set_left_operand); + ClassDB::bind_method(D_METHOD("get_left_operand"), &BeehaveBlackboardCompare::get_left_operand); + ClassDB::bind_method(D_METHOD("set_right_operand", "right_operand"), &BeehaveBlackboardCompare::set_right_operand); + ClassDB::bind_method(D_METHOD("get_right_operand"), &BeehaveBlackboardCompare::get_right_operand); + ClassDB::bind_method(D_METHOD("set_comparison_operator", "comparison_operator"), &BeehaveBlackboardCompare::set_comparison_operator); + ClassDB::bind_method(D_METHOD("get_comparison_operator"), &BeehaveBlackboardCompare::get_comparison_operator); + + // exports + ADD_PROPERTY(PropertyInfo(Variant::STRING, "left_operand", PropertyHint::PROPERTY_HINT_PLACEHOLDER_TEXT, "Insert an expression..."), "set_left_operand", "get_left_operand"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "right_operand", PropertyHint::PROPERTY_HINT_PLACEHOLDER_TEXT, "Insert an expression..."), "set_right_operand", "get_right_operand"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "comparison_operator", PropertyHint::PROPERTY_HINT_ENUM, "==, !=, >, <, >=, <="), "set_comparison_operator", "get_comparison_operator"); +} + +BeehaveBlackboardCompare::BeehaveBlackboardCompare() { + left_expression.instantiate(); + right_expression.instantiate(); +} + +BeehaveBlackboardCompare::~BeehaveBlackboardCompare() { + +} + +void BeehaveBlackboardCompare::set_left_operand(String left_operand) { + this->left_operand = left_operand; + + Error error = left_expression->parse(left_operand); + if (error != Error::OK) { + if (!Engine::get_singleton()->is_editor_hint()) { + UtilityFunctions::push_error("[BlackboardSet] Couldn't parse expression with source: ", left_operand, " Error text: ", left_expression->get_error_text()); + } + is_left_expression_successfully_parsed = false; + } + else { + execute_failure_printed = false; + is_left_expression_successfully_parsed = true; + } + + update_configuration_warnings(); +} + +String BeehaveBlackboardCompare::get_left_operand() const { + return this->left_operand; +} + +void BeehaveBlackboardCompare::set_right_operand(String right_operand) { + this->right_operand = right_operand; + + Error error = right_expression->parse(right_operand); + if (error != Error::OK) { + if (!Engine::get_singleton()->is_editor_hint()) { + UtilityFunctions::push_error("[BlackboardSet] Couldn't parse expression with source: ", right_operand, " Error text: ", right_expression->get_error_text()); + } + is_right_expression_successfully_parsed = false; + } + else { + execute_failure_printed = false; + is_right_expression_successfully_parsed = true; + } + + update_configuration_warnings(); +} + +String BeehaveBlackboardCompare::get_right_operand() const { + return this->right_operand; +} + +void BeehaveBlackboardCompare::set_comparison_operator(BeehaveBlackboardCompare::ComparisonOperator comparison_operator) { + this->comparison_operator = comparison_operator; +} + +BeehaveBlackboardCompare::ComparisonOperator BeehaveBlackboardCompare::get_comparison_operator() const { + return this->comparison_operator; +} + +BeehaveTickStatus BeehaveBlackboardCompare::tick(Ref context) { + if (!is_left_expression_successfully_parsed || !is_right_expression_successfully_parsed) { + return BeehaveTickStatus::FAILURE; + } + + Variant left = left_expression->execute(Array(), context->get_blackboard(), false); + + if (left_expression->has_execute_failed()) { + if (!execute_failure_printed && !Engine::get_singleton()->is_editor_hint()) { + // Until the expression is changed, it will (likely) keep failing. Don't flood the output with errors. + UtilityFunctions::push_error("[BlackboardCompare] Couldn't execute left operand with source: ", left_operand, " Error text: ", left_expression->get_error_text()); + execute_failure_printed = true; + } + return BeehaveTickStatus::FAILURE; + } + + Variant right = right_expression->execute(Array(), context->get_blackboard(), false); + + if (right_expression->has_execute_failed()) { + if (!execute_failure_printed && !Engine::get_singleton()->is_editor_hint()) { + // Until the expression is changed, it will (likely) keep failing. Don't flood the output with errors. + UtilityFunctions::push_error("[BlackboardCompare] Couldn't execute right operand with source: ", right_operand, " Error text: ", right_expression->get_error_text()); + execute_failure_printed = true; + } + return BeehaveTickStatus::FAILURE; + } + + Variant returned; + bool valid; + switch(comparison_operator) { + case EQUAL: + Variant::evaluate(Variant::Operator::OP_EQUAL, left, right, returned, valid); + break; + case NOT_EQUAL: + Variant::evaluate(Variant::Operator::OP_NOT_EQUAL, left, right, returned, valid); + break; + case GREATER: + Variant::evaluate(Variant::Operator::OP_GREATER, left, right, returned, valid); + break; + case LESS: + Variant::evaluate(Variant::Operator::OP_LESS, left, right, returned, valid); + break; + case GREATER_EQUAL: + Variant::evaluate(Variant::Operator::OP_GREATER_EQUAL, left, right, returned, valid); + break; + case LESS_EQUAL: + Variant::evaluate(Variant::Operator::OP_LESS_EQUAL, left, right, returned, valid); + break; + } + + bool result = returned.booleanize(); + return valid && result ? SUCCESS : FAILURE; +} + +PackedStringArray BeehaveBlackboardCompare::_get_configuration_warnings() const { + PackedStringArray warnings = BeehaveCondition::_get_configuration_warnings(); + if (!is_left_expression_successfully_parsed) { + warnings.push_back(vformat("Couldn't parse left operand with source: %s Error text: %s", left_operand, left_expression->get_error_text())); + } + if (!is_right_expression_successfully_parsed) { + warnings.push_back(vformat("Couldn't parse right operand with source: %s Error text: %s", right_operand, right_expression->get_error_text())); + } + return warnings; +} \ No newline at end of file diff --git a/extension/src/nodes/leaves/beehave_blackboard_compare.h b/extension/src/nodes/leaves/beehave_blackboard_compare.h new file mode 100644 index 00000000..2f14c6ff --- /dev/null +++ b/extension/src/nodes/leaves/beehave_blackboard_compare.h @@ -0,0 +1,86 @@ +/**************************************************************************/ +/* beehave_blackboard_compare.h */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef BEEHAVE_BLACKBOARD_COMPARE +#define BEEHAVE_BLACKBOARD_COMPARE + +#include "beehave_condition.h" +#include + +namespace godot { + +class BeehaveBlackboardCompare : public BeehaveCondition { + GDCLASS(BeehaveBlackboardCompare, BeehaveCondition); + +public: + enum ComparisonOperator { + EQUAL = 0, + NOT_EQUAL = 1, + GREATER = 2, + LESS = 3, + GREATER_EQUAL = 4, + LESS_EQUAL = 5, + }; + + String left_operand; + String right_operand; + ComparisonOperator comparison_operator = ComparisonOperator::EQUAL; + + private: + Ref left_expression; + Ref right_expression; + bool is_left_expression_successfully_parsed = false; + bool is_right_expression_successfully_parsed = false; + bool execute_failure_printed = false; + +protected: + static void _bind_methods(); + +public: + BeehaveBlackboardCompare(); + ~BeehaveBlackboardCompare(); + + void set_left_operand(String left_operand); + String get_left_operand() const; + + void set_right_operand(String right_operand); + String get_right_operand() const; + + void set_comparison_operator(BeehaveBlackboardCompare::ComparisonOperator comparison_operator); + BeehaveBlackboardCompare::ComparisonOperator get_comparison_operator() const; + + BeehaveTickStatus tick(Ref context); + + PackedStringArray _get_configuration_warnings() const override; +}; +} + +VARIANT_ENUM_CAST(BeehaveBlackboardCompare::ComparisonOperator); + +#endif //BEEHAVE_BLACKBOARD_COMPARE \ No newline at end of file diff --git a/extension/src/nodes/leaves/beehave_blackboard_erase.cpp b/extension/src/nodes/leaves/beehave_blackboard_erase.cpp new file mode 100644 index 00000000..092de4ef --- /dev/null +++ b/extension/src/nodes/leaves/beehave_blackboard_erase.cpp @@ -0,0 +1,63 @@ +/**************************************************************************/ +/* beehave_blackboard_erase.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "beehave_blackboard_erase.h" +#include "nodes/beehave_blackboard.h" + +using namespace godot; + +void BeehaveBlackboardErase::_bind_methods() { + // methods + ClassDB::bind_method(D_METHOD("set_key", "key"), &BeehaveBlackboardErase::set_key); + ClassDB::bind_method(D_METHOD("get_key"), &BeehaveBlackboardErase::get_key); + + // exports + ADD_PROPERTY(PropertyInfo(Variant::STRING, "key", PropertyHint::PROPERTY_HINT_PLACEHOLDER_TEXT, "Insert a key name..."), "set_key", "get_key"); +} + +BeehaveBlackboardErase::BeehaveBlackboardErase() { + +} + +BeehaveBlackboardErase::~BeehaveBlackboardErase() { + +} + +void BeehaveBlackboardErase::set_key(String key) { + this->key = key; +} + +String BeehaveBlackboardErase::get_key() const { + return this->key; +} + +BeehaveTickStatus BeehaveBlackboardErase::tick(Ref context) { + bool success = context->get_blackboard()->erase_value(key); + return success ? SUCCESS : FAILURE; +} \ No newline at end of file diff --git a/extension/src/nodes/leaves/beehave_blackboard_erase.h b/extension/src/nodes/leaves/beehave_blackboard_erase.h new file mode 100644 index 00000000..90b76f67 --- /dev/null +++ b/extension/src/nodes/leaves/beehave_blackboard_erase.h @@ -0,0 +1,56 @@ +/**************************************************************************/ +/* beehave_blackboard_erase.h */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef BEEHAVE_BLACKBOARD_ERASE +#define BEEHAVE_BLACKBOARD_ERASE + +#include "beehave_action.h" + +namespace godot { + +class BeehaveBlackboardErase : public BeehaveAction { + GDCLASS(BeehaveBlackboardErase, BeehaveAction); + + String key; + + protected: + static void _bind_methods(); + + public: + BeehaveBlackboardErase(); + ~BeehaveBlackboardErase(); + + void set_key(String key); + String get_key() const; + + BeehaveTickStatus tick(Ref context); +}; +} + +#endif //BEEHAVE_BLACKBOARD_ERASE \ No newline at end of file diff --git a/extension/src/nodes/leaves/beehave_blackboard_has.cpp b/extension/src/nodes/leaves/beehave_blackboard_has.cpp new file mode 100644 index 00000000..318fb8e8 --- /dev/null +++ b/extension/src/nodes/leaves/beehave_blackboard_has.cpp @@ -0,0 +1,63 @@ +/**************************************************************************/ +/* beehave_blackboard_has.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "beehave_blackboard_has.h" +#include "nodes/beehave_blackboard.h" + +using namespace godot; + +void BeehaveBlackboardHas::_bind_methods() { + // methods + ClassDB::bind_method(D_METHOD("set_key", "key"), &BeehaveBlackboardHas::set_key); + ClassDB::bind_method(D_METHOD("get_key"), &BeehaveBlackboardHas::get_key); + + // exports + ADD_PROPERTY(PropertyInfo(Variant::STRING, "key", PropertyHint::PROPERTY_HINT_PLACEHOLDER_TEXT, "Insert a key name..."), "set_key", "get_key"); +} + +BeehaveBlackboardHas::BeehaveBlackboardHas() { + +} + +BeehaveBlackboardHas::~BeehaveBlackboardHas() { + +} + +void BeehaveBlackboardHas::set_key(String key) { + this->key = key; +} + +String BeehaveBlackboardHas::get_key() const { + return this->key; +} + +BeehaveTickStatus BeehaveBlackboardHas::tick(Ref context) { + bool success = context->get_blackboard()->has_value(key); + return success ? SUCCESS : FAILURE; +} \ No newline at end of file diff --git a/extension/src/nodes/leaves/beehave_blackboard_has.h b/extension/src/nodes/leaves/beehave_blackboard_has.h new file mode 100644 index 00000000..0a29cd3b --- /dev/null +++ b/extension/src/nodes/leaves/beehave_blackboard_has.h @@ -0,0 +1,56 @@ +/**************************************************************************/ +/* beehave_blackboard_has.h */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef BEEHAVE_BLACKBOARD_HAS +#define BEEHAVE_BLACKBOARD_HAS + +#include "beehave_condition.h" + +namespace godot { + +class BeehaveBlackboardHas : public BeehaveCondition { + GDCLASS(BeehaveBlackboardHas, BeehaveCondition); + + String key; + + protected: + static void _bind_methods(); + + public: + BeehaveBlackboardHas(); + ~BeehaveBlackboardHas(); + + void set_key(String key); + String get_key() const; + + BeehaveTickStatus tick(Ref context); +}; +} + +#endif //BEEHAVE_BLACKBOARD_HAS \ No newline at end of file diff --git a/extension/src/nodes/leaves/beehave_blackboard_set.cpp b/extension/src/nodes/leaves/beehave_blackboard_set.cpp new file mode 100644 index 00000000..d0885816 --- /dev/null +++ b/extension/src/nodes/leaves/beehave_blackboard_set.cpp @@ -0,0 +1,113 @@ +/**************************************************************************/ +/* beehave_blackboard_set.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "beehave_blackboard_set.h" +#include "nodes/beehave_blackboard.h" +#include +#include + +using namespace godot; + +void BeehaveBlackboardSet::_bind_methods() { + // methods + ClassDB::bind_method(D_METHOD("set_key", "key"), &BeehaveBlackboardSet::set_key); + ClassDB::bind_method(D_METHOD("get_key"), &BeehaveBlackboardSet::get_key); + ClassDB::bind_method(D_METHOD("set_value", "value"), &BeehaveBlackboardSet::set_value); + ClassDB::bind_method(D_METHOD("get_value"), &BeehaveBlackboardSet::get_value); + + // exports + ADD_PROPERTY(PropertyInfo(Variant::STRING, "key", PropertyHint::PROPERTY_HINT_PLACEHOLDER_TEXT, "Insert a key name..."), "set_key", "get_key"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "value", PropertyHint::PROPERTY_HINT_PLACEHOLDER_TEXT, "Insert a value expression..."), "set_value", "get_value"); +} + +BeehaveBlackboardSet::BeehaveBlackboardSet() { + value_expression.instantiate(); +} + +BeehaveBlackboardSet::~BeehaveBlackboardSet() { + +} + +void BeehaveBlackboardSet::set_key(String key) { + this->key = key; +} + +String BeehaveBlackboardSet::get_key() const { + return this->key; +} + +void BeehaveBlackboardSet::set_value(String value) { + this->value = value; + + Error error = value_expression->parse(value); + if (error != Error::OK) { + if (!Engine::get_singleton()->is_editor_hint()) { + UtilityFunctions::push_error("[BlackboardSet] Couldn't parse expression with source: ", value, " Error text: ", value_expression->get_error_text()); + } + is_expression_successfully_parsed = false; + } + else { + execute_failure_printed = false; + is_expression_successfully_parsed = true; + } + + update_configuration_warnings(); +} + +String BeehaveBlackboardSet::get_value() const { + return this->value; +} + +BeehaveTickStatus BeehaveBlackboardSet::tick(Ref context) { + if (!is_expression_successfully_parsed) { + return BeehaveTickStatus::FAILURE; + } + + Variant final_value = value_expression->execute(Array(), context->get_blackboard(), false); + + if (value_expression->has_execute_failed()) { + if (!execute_failure_printed && !Engine::get_singleton()->is_editor_hint()) { + // Until the expression is changed, it will (likely) keep failing. Don't flood the output with errors. + UtilityFunctions::push_error("[BlackboardSet] Couldn't execute expression with source: ", value, " Error text: ", value_expression->get_error_text()); + execute_failure_printed = true; + } + return BeehaveTickStatus::FAILURE; + } + + context->get_blackboard()->set_value(key, final_value); + return BeehaveTickStatus::SUCCESS; +} + +PackedStringArray BeehaveBlackboardSet::_get_configuration_warnings() const { + PackedStringArray warnings = BeehaveAction::_get_configuration_warnings(); + if (!is_expression_successfully_parsed) { + warnings.push_back(vformat("Couldn't parse expression with source: %s Error text: %s", value, value_expression->get_error_text())); + } + return warnings; +} \ No newline at end of file diff --git a/extension/src/nodes/leaves/beehave_blackboard_set.h b/extension/src/nodes/leaves/beehave_blackboard_set.h new file mode 100644 index 00000000..364e8bb8 --- /dev/null +++ b/extension/src/nodes/leaves/beehave_blackboard_set.h @@ -0,0 +1,68 @@ +/**************************************************************************/ +/* beehave_blackboard_set.h */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef BEEHAVE_BLACKBOARD_SET +#define BEEHAVE_BLACKBOARD_SET + +#include "beehave_action.h" +#include + +namespace godot { + +class BeehaveBlackboardSet : public BeehaveAction { + GDCLASS(BeehaveBlackboardSet, BeehaveAction); + + String key; + String value; + + private: + Ref value_expression; + bool is_expression_successfully_parsed = false; + bool execute_failure_printed = false; + + protected: + static void _bind_methods(); + + public: + BeehaveBlackboardSet(); + ~BeehaveBlackboardSet(); + + void set_key(String key); + String get_key() const; + + void set_value(String value); + String get_value() const; + + BeehaveTickStatus tick(Ref context); + + PackedStringArray _get_configuration_warnings() const override; +}; +} + +#endif //BEEHAVE_BLACKBOARD_SET \ No newline at end of file diff --git a/extension/src/nodes/leaves/beehave_condition.cpp b/extension/src/nodes/leaves/beehave_condition.cpp new file mode 100644 index 00000000..9376ea1f --- /dev/null +++ b/extension/src/nodes/leaves/beehave_condition.cpp @@ -0,0 +1,43 @@ +/**************************************************************************/ +/* beehave_condition.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "beehave_condition.h" + +using namespace godot; + +void BeehaveCondition::_bind_methods() { +} + +BeehaveCondition::BeehaveCondition() { + +} + +BeehaveCondition ::~BeehaveCondition() { + +} \ No newline at end of file diff --git a/extension/src/nodes/leaves/beehave_condition.h b/extension/src/nodes/leaves/beehave_condition.h new file mode 100644 index 00000000..9061239f --- /dev/null +++ b/extension/src/nodes/leaves/beehave_condition.h @@ -0,0 +1,49 @@ +/**************************************************************************/ +/* beehave_condition.h */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef BEEHAVE_CONDITION +#define BEEHAVE_CONDITION + +#include "beehave_leaf.h" + +namespace godot { + +class BeehaveCondition : public BeehaveLeaf { + GDCLASS(BeehaveCondition, BeehaveLeaf); + + protected: + static void _bind_methods(); + + public: + BeehaveCondition(); + ~BeehaveCondition(); +}; +} + +#endif //BEEHAVE_CONDITION \ No newline at end of file diff --git a/extension/src/nodes/leaves/beehave_leaf.cpp b/extension/src/nodes/leaves/beehave_leaf.cpp new file mode 100644 index 00000000..cf43a9f0 --- /dev/null +++ b/extension/src/nodes/leaves/beehave_leaf.cpp @@ -0,0 +1,60 @@ +/**************************************************************************/ +/* beehave_leaf.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "beehave_leaf.h" + +using namespace godot; + +void BeehaveLeaf::_bind_methods() { +} + +BeehaveLeaf::BeehaveLeaf() { + +} + +BeehaveLeaf::~BeehaveLeaf() { + +} + +PackedStringArray BeehaveLeaf::_get_configuration_warnings() const { + PackedStringArray warnings = BeehaveTreeNode::_get_configuration_warnings(); + + Callable is_beehave_node = callable_mp_static(&BeehaveLeaf::_is_beehave_node); + bool found_beehave_node = get_children().any(is_beehave_node); + + if (found_beehave_node) { + warnings.push_back("Leaf nodes shouldn't have any child nodes. They won't be ticked."); + } + return warnings; +} + +bool BeehaveLeaf::_is_beehave_node(Node* node) { + BeehaveTreeNode* beehave_node = cast_to(node); + return beehave_node != nullptr ? true : false; +} \ No newline at end of file diff --git a/extension/src/nodes/leaves/beehave_leaf.h b/extension/src/nodes/leaves/beehave_leaf.h new file mode 100644 index 00000000..15842f45 --- /dev/null +++ b/extension/src/nodes/leaves/beehave_leaf.h @@ -0,0 +1,54 @@ +/**************************************************************************/ +/* beehave_leaf.h */ +/**************************************************************************/ +/* This file is part of: */ +/* BEEHAVE */ +/* https://bitbra.in/beehave */ +/**************************************************************************/ +/* Copyright (c) 2024-present Beehave Contributors. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef BEEHAVE_LEAF +#define BEEHAVE_LEAF + +#include "nodes/beehave_tree_node.h" + +namespace godot { + +class BeehaveLeaf : public BeehaveTreeNode { + GDCLASS(BeehaveLeaf, BeehaveTreeNode); + + protected: + static void _bind_methods(); + + public: + BeehaveLeaf(); + ~BeehaveLeaf(); + + PackedStringArray _get_configuration_warnings() const override; + + private: + static bool _is_beehave_node(Node* node); +}; +} + +#endif //BEEHAVE_LEAF \ No newline at end of file diff --git a/extension/src/register_types.cpp b/extension/src/register_types.cpp index e7cf4365..1cd1cdd9 100644 --- a/extension/src/register_types.cpp +++ b/extension/src/register_types.cpp @@ -1,16 +1,85 @@ #include "register_types.h" #include -#include -#include -#include +#include +#include +#include + +#include "beehave_context.h" +#include "nodes/beehave_blackboard.h" +#include "nodes/beehave_tree.h" +#include "nodes/beehave_tree_node.h" +#include "nodes/leaves/beehave_leaf.h" +#include "nodes/leaves/beehave_action.h" +#include "nodes/leaves/beehave_condition.h" +#include "nodes/leaves/beehave_blackboard_compare.h" +#include "nodes/leaves/beehave_blackboard_erase.h" +#include "nodes/leaves/beehave_blackboard_has.h" +#include "nodes/leaves/beehave_blackboard_set.h" +#include "nodes/decorators/beehave_decorator.h" +#include "nodes/decorators/beehave_succeeder.h" +#include "nodes/decorators/beehave_failer.h" +#include "nodes/decorators/beehave_inverter.h" +#include "nodes/decorators/beehave_cooldown.h" +#include "nodes/decorators/beehave_limiter.h" +#include "nodes/decorators/beehave_time_limiter.h" +#include "nodes/decorators/beehave_delayer.h" +#include "nodes/decorators/beehave_repeater.h" +#include "nodes/decorators/beehave_until_fail.h" +#include "nodes/composites/beehave_composite.h" +#include "nodes/composites/beehave_composite_random.h" +#include "nodes/composites/beehave_selector.h" +#include "nodes/composites/beehave_selector_reactive.h" +#include "nodes/composites/beehave_selector_random.h" +#include "nodes/composites/beehave_sequence.h" +#include "nodes/composites/beehave_sequence_reactive.h" +#include "nodes/composites/beehave_sequence_star.h" +#include "nodes/composites/beehave_sequence_random.h" +#include "nodes/composites/beehave_simple_parallel.h" using namespace godot; -void initialize_beehave_types(ModuleInitializationLevel p_level) -{ +void initialize_beehave_types(ModuleInitializationLevel p_level) { if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { return; } + // base nodes + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_abstract_class(); + ClassDB::register_class(); + + // leafs + ClassDB::register_abstract_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + + // decorators + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + + // composites + ClassDB::register_abstract_class(); + ClassDB::register_abstract_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); } void uninitialize_beehave_types(ModuleInitializationLevel p_level) { @@ -19,18 +88,18 @@ void uninitialize_beehave_types(ModuleInitializationLevel p_level) { } } -extern "C" -{ +extern "C" { - // Initialization. +// Initialization. - GDExtensionBool GDE_EXPORT beehave_library_init(GDExtensionInterfaceGetProcAddress p_get_proc_address, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization) { - GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library, r_initialization); +GDExtensionBool GDE_EXPORT +beehave_library_init(GDExtensionInterfaceGetProcAddress p_get_proc_address, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization) { + GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library, r_initialization); - init_obj.register_initializer(initialize_beehave_types); - init_obj.register_terminator(uninitialize_beehave_types); - init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SCENE); + init_obj.register_initializer(initialize_beehave_types); + init_obj.register_terminator(uninitialize_beehave_types); + init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_CORE); - return init_obj.init(); - } -} \ No newline at end of file + return init_obj.init(); +} +} diff --git a/extension/src/register_types.h b/extension/src/register_types.h index 5de70794..9cb4b2f1 100644 --- a/extension/src/register_types.h +++ b/extension/src/register_types.h @@ -1,7 +1,11 @@ #ifndef BEEHAVE_REGISTER_TYPES_H #define BEEHAVE_REGISTER_TYPES_H +#include + +using namespace godot; + void initialize_beehave_types(); void uninitialize_beehave_types(); -#endif // BEEHAVE_REGISTER_TYPES_H \ No newline at end of file +#endif // BEEHAVE_REGISTER_TYPES_H diff --git a/godot-cpp b/godot-cpp index 78ffea5b..f9008591 160000 --- a/godot-cpp +++ b/godot-cpp @@ -1 +1 @@ -Subproject commit 78ffea5b136f3178c31cddb28f6b963ceaa89420 +Subproject commit f90085917b16c3daff7b2d195db9bf222119eea1 diff --git a/project.godot b/project.godot index 487d57d2..6220e385 100644 --- a/project.godot +++ b/project.godot @@ -12,16 +12,11 @@ config_version=5 config/name="Beehave" run/main_scene="res://examples/beehave_test_scene.tscn" -config/features=PackedStringArray("4.2") +config/features=PackedStringArray("4.3") boot_splash/image="res://splash.png" boot_splash/fullsize=false config/icon="res://icon.png" -[autoload] - -BeehaveGlobalMetrics="*res://addons/beehave/metrics/beehave_global_metrics.gd" -BeehaveGlobalDebugger="*res://addons/beehave/debug/global_debugger.gd" - [dotnet] project/assembly_name="Beehave" @@ -42,30 +37,30 @@ settings/test/test_lookup_folder="test/" ui_left={ "deadzone": 0.5, -"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194319,"physical_keycode":0,"key_label":0,"unicode":4194319,"echo":false,"script":null) +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194319,"physical_keycode":0,"key_label":0,"unicode":4194319,"location":0,"echo":false,"script":null) , Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":13,"pressure":0.0,"pressed":false,"script":null) -, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194319,"key_label":0,"unicode":0,"echo":false,"script":null) +, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194319,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) ] } ui_right={ "deadzone": 0.5, -"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194321,"physical_keycode":0,"key_label":0,"unicode":4194321,"echo":false,"script":null) +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194321,"physical_keycode":0,"key_label":0,"unicode":4194321,"location":0,"echo":false,"script":null) , Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":14,"pressure":0.0,"pressed":false,"script":null) -, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194321,"key_label":0,"unicode":0,"echo":false,"script":null) +, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194321,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) ] } ui_up={ "deadzone": 0.5, -"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194320,"physical_keycode":0,"key_label":0,"unicode":4194320,"echo":false,"script":null) +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194320,"physical_keycode":0,"key_label":0,"unicode":4194320,"location":0,"echo":false,"script":null) , Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":11,"pressure":0.0,"pressed":false,"script":null) -, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194320,"key_label":0,"unicode":0,"echo":false,"script":null) +, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194320,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) ] } ui_down={ "deadzone": 0.5, -"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194322,"physical_keycode":0,"key_label":0,"unicode":4194322,"echo":false,"script":null) +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194322,"physical_keycode":0,"key_label":0,"unicode":4194322,"location":0,"echo":false,"script":null) , Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":12,"pressure":0.0,"pressed":false,"script":null) -, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194322,"key_label":0,"unicode":0,"echo":false,"script":null) +, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194322,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) ] } diff --git a/test/actions/clear_count_action.gd b/test/actions/clear_count_action.gd index 2436f323..5341b9d7 100644 --- a/test/actions/clear_count_action.gd +++ b/test/actions/clear_count_action.gd @@ -1,10 +1,10 @@ -class_name ClearCountAction extends ActionLeaf +class_name ClearCountAction extends BeehaveAction @export var key = "custom_value" -func tick(_actor: Node, blackboard: Blackboard) -> int: - if blackboard.has_value(key): - blackboard.erase_value(key) +func tick(context: BeehaveContext) -> int: + if context.get_blackboard().has_value(key): + context.get_blackboard().erase_value(key) return SUCCESS else: return FAILURE diff --git a/test/actions/count_up_action.gd b/test/actions/count_up_action.gd index e83ae0e5..fa79f7e9 100644 --- a/test/actions/count_up_action.gd +++ b/test/actions/count_up_action.gd @@ -1,17 +1,17 @@ -class_name CountUpAction extends ActionLeaf +class_name CountUpAction extends BeehaveAction @export var key = "custom_value" var count = 0 var status = SUCCESS -func tick(_actor: Node, blackboard: Blackboard) -> int: +func _tick(context) -> BeehaveTickStatus: count += 1 - blackboard.set_value(key, count) + context.get_blackboard().set_value(key, count) return status -func interrupt(_actor: Node, blackboard: Blackboard) -> void: +func interrupt(context: BeehaveContext) -> void: count = 0 - blackboard.set_value(key, count) + context.get_blackboard().set_value(key, count) status = FAILURE diff --git a/test/actions/mock_action.gd b/test/actions/mock_action.gd index 5909446c..eae70631 100644 --- a/test/actions/mock_action.gd +++ b/test/actions/mock_action.gd @@ -1,5 +1,5 @@ class_name MockAction -extends ActionLeaf +extends BeehaveAction @export_enum("Success", "Failure") var final_result: int = 0 @export var running_frame_count: int = 0 @@ -11,12 +11,12 @@ signal interrupted(actor, blackboard) var tick_count: int = 0 -func before_run(actor: Node, blackboard: Blackboard) -> void: +func before_run(context: BeehaveContext) -> void: tick_count = 0 - started_running.emit(actor, blackboard) + started_running.emit(context.get_actor(), context.get_blackboard()) -func tick(_actor: Node, _blackboard: Blackboard) -> int: +func tick(_context: BeehaveContext) -> int: if tick_count < running_frame_count: tick_count += 1 return RUNNING @@ -24,10 +24,10 @@ func tick(_actor: Node, _blackboard: Blackboard) -> int: return final_result -func interrupt(actor: Node, blackboard: Blackboard) -> void: - interrupted.emit(actor, blackboard) +func interrupt(context: BeehaveContext) -> void: + interrupted.emit(context.get_actor(), context.get_blackboard()) -func after_run(actor: Node, blackboard: Blackboard) -> void: +func after_run(context: BeehaveContext) -> void: tick_count = 0 - stopped_running.emit(actor, blackboard) + stopped_running.emit(context.get_actor(), context.get_blackboard()) diff --git a/test/beehave_tree_test.gd b/test/beehave_tree_test.gd index e68a1fa0..277f61e4 100644 --- a/test/beehave_tree_test.gd +++ b/test/beehave_tree_test.gd @@ -4,67 +4,65 @@ extends GdUnitTestSuite @warning_ignore("unused_parameter") @warning_ignore("return_value_discarded") -# TestSuite generated from -const __source = "res://addons/beehave/nodes/beehave_tree.gd" - func create_scene() -> Node2D: return auto_free(load("res://test/unit_test_scene.tscn").instantiate()) func create_tree() -> BeehaveTree: - return auto_free(load(__source).new()) + var tree = auto_free(BeehaveTree.new()) as BeehaveTree + tree.process_thread = BeehaveTree.IDLE + return tree func test_normal_tick() -> void: var scene = create_scene() - scene_runner(scene) - scene.beehave_tree._physics_process(1.0) - assert_that(scene.beehave_tree.status).is_equal(BeehaveNode.SUCCESS) + var runner = scene_runner(scene) + await runner.simulate_frames(1) + assert_that(scene.beehave_tree.get_tick_status()).is_equal(BeehaveTreeNode.SUCCESS) func test_low_tick_rate() -> void: var scene = create_scene() - scene_runner(scene) - scene.beehave_tree.tick_rate = 3 - scene.beehave_tree._physics_process(1.0) - assert_that(scene.beehave_tree.status).is_equal(-1) - scene.beehave_tree._physics_process(1.0) - assert_that(scene.beehave_tree.status).is_equal(-1) - scene.beehave_tree._physics_process(1.0) - assert_that(scene.beehave_tree.status).is_equal(BeehaveNode.SUCCESS) + var runner = scene_runner(scene) + scene.beehave_tree.set_tick_rate(3) + await runner.simulate_frames(1) + assert_that(scene.beehave_tree.get_tick_status()).is_equal(-1) + await runner.simulate_frames(1) + assert_that(scene.beehave_tree.get_tick_status()).is_equal(-1) + await runner.simulate_frames(1) + assert_that(scene.beehave_tree.get_tick_status()).is_equal(BeehaveTreeNode.SUCCESS) func test_low_tick_rate_last_tick() -> void: var scene = create_scene() - scene_runner(scene) - scene.beehave_tree.tick_rate = 3 - scene.beehave_tree.last_tick = 1 - scene.beehave_tree._physics_process(1.0) - assert_that(scene.beehave_tree.status).is_equal(-1) - scene.beehave_tree._physics_process(1.0) - assert_that(scene.beehave_tree.status).is_equal(BeehaveNode.SUCCESS) - -func test_nothing_running_before_first_tick() -> void: - var scene = create_scene() - scene_runner(scene) - assert_that(scene.beehave_tree.get_running_action()).is_null() - assert_that(scene.beehave_tree.get_last_condition()).is_null() - assert_that(scene.beehave_tree.get_last_condition_status()).is_equal("") - -func test_get_last_condition() -> void: - var scene = create_scene() - var runner := scene_runner(scene) - await runner.simulate_frames(100) - assert_that(scene.beehave_tree.get_running_action()).is_null() - assert_that(scene.beehave_tree.get_last_condition()).is_not_null() - assert_that(scene.beehave_tree.get_last_condition_status()).is_equal('SUCCESS') + var runner = scene_runner(scene) + scene.beehave_tree.set_tick_rate(3) + await runner.simulate_frames(1) + assert_that(scene.beehave_tree.get_tick_status()).is_equal(BeehaveTreeNode.PENDING) + await runner.simulate_frames(1) + assert_that(scene.beehave_tree.get_tick_status()).is_equal(BeehaveTreeNode.SUCCESS) + +#func test_nothing_running_before_first_tick() -> void: +# var scene = create_scene() +# scene_runner(scene) +# assert_that(scene.beehave_tree.get_running_action()).is_null() +# assert_that(scene.beehave_tree.get_last_condition()).is_null() +# assert_that(scene.beehave_tree.get_last_condition_status()).is_equal("") + +#func test_get_last_condition() -> void: +# var scene = create_scene() +# var runner := scene_runner(scene) +# await runner.simulate_frames(100) +# assert_that(scene.beehave_tree.get_running_action()).is_null() +# assert_that(scene.beehave_tree.get_last_condition()).is_not_null() +# assert_that(scene.beehave_tree.get_last_condition_status()).is_equal('SUCCESS') func test_disabled() -> void: var scene = create_scene() var runner := scene_runner(scene) scene.beehave_tree.disable() await runner.simulate_frames(50) - assert_bool(scene.beehave_tree.enabled).is_false() - assert_that(scene.beehave_tree.get_running_action()).is_null() - assert_that(scene.beehave_tree.get_last_condition()).is_null() + assert_bool(scene.beehave_tree.is_enabled()).is_false() + #assert_that(scene.beehave_tree.get_running_action()).is_null() + #assert_that(scene.beehave_tree.get_last_condition()).is_null() func test_reenabled() -> void: @@ -74,26 +72,26 @@ func test_reenabled() -> void: scene.beehave_tree.enable() await runner.simulate_frames(50) assert_bool(scene.beehave_tree.enabled).is_true() - assert_that(scene.beehave_tree.get_last_condition()).is_not_null() - - -func test_interrupt_running_action() -> void: - var scene = create_scene() - scene_runner(scene) - scene.count_up_action.status = BeehaveNode.RUNNING - scene.beehave_tree._physics_process(1.0) - scene.beehave_tree._physics_process(1.0) - assert_that(scene.beehave_tree.blackboard.get_value("custom_value")).is_equal(2) - scene.beehave_tree.interrupt() - assert_that(scene.beehave_tree.blackboard.get_value("custom_value")).is_equal(0) - assert_that(scene.count_up_action.status).is_equal(BeehaveNode.FAILURE) - - -func test_blackboard_not_initialized() -> void: - var tree = create_tree() - tree.actor = auto_free(Node2D.new()) - var always_succeed = auto_free(AlwaysSucceedDecorator.new()) as AlwaysSucceedDecorator - always_succeed.add_child(auto_free(ActionLeaf.new())) - tree.add_child(always_succeed) - var result = tree.tick() - assert_that(result).is_equal(BeehaveNode.SUCCESS) + #assert_that(scene.beehave_tree.get_last_condition()).is_not_null() + + +#func test_interrupt_running_action() -> void: +# var scene = create_scene() +# scene_runner(scene) +# scene.count_up_action.status = BeehaveTreeNode.RUNNING +# scene.beehave_tree._physics_process(1.0) +# scene.beehave_tree._physics_process(1.0) +# assert_that(scene.beehave_tree.blackboard.get_value("custom_value")).is_equal(2) +# scene.beehave_tree.interrupt() +# assert_that(scene.beehave_tree.blackboard.get_value("custom_value")).is_equal(0) +# assert_that(scene.count_up_action.status).is_equal(BeehaveTreeNode.FAILURE) + + +#func test_blackboard_not_initialized() -> void: +# var tree = create_tree() +# tree.actor = auto_free(Node2D.new()) +# var always_succeed = auto_free(AlwaysSucceedDecorator.new()) as AlwaysSucceedDecorator +# always_succeed.add_child(auto_free(BeehaveAction.new())) +# tree.add_child(always_succeed) +# var result = tree.tick() +# assert_that(result).is_equal(BeehaveTreeNode.SUCCESS) diff --git a/test/before_after_run_test.gd b/test/before_after_run_test.gd index 44ec0397..ea12665b 100644 --- a/test/before_after_run_test.gd +++ b/test/before_after_run_test.gd @@ -5,20 +5,17 @@ extends GdUnitTestSuite const __mock_action = "res://test/actions/mock_action.gd" -const __succedeer = "res://addons/beehave/nodes/decorators/succeeder.gd" -const __blackboard = "res://addons/beehave/blackboard.gd" -const __tree = "res://addons/beehave/nodes/beehave_tree.gd" var tree: BeehaveTree -var action: ActionLeaf +var action: BeehaveAction func before_test() -> void: - tree = auto_free(load(__tree).new()) + tree = auto_free(BeehaveTree.new()) action = auto_free(load(__mock_action).new()) - var succeeder = auto_free(load(__succedeer).new()) + var succeeder = auto_free(BeehaveSucceeder.new()) var actor = auto_free(Node2D.new()) - var blackboard = auto_free(load(__blackboard).new()) + var blackboard = auto_free(BeehaveBlackboard.new()) tree.add_child(succeeder) succeeder.add_child(action) @@ -41,10 +38,10 @@ func test_action_after_run() -> void: assert_bool(tree.blackboard.get_value("entered", false)).is_false() assert_bool(tree.blackboard.get_value("exited", false)).is_false() - assert_int(tree.tick()).is_equal(BeehaveNode.RUNNING) + assert_int(tree.tick()).is_equal(BeehaveTreeNode.RUNNING) assert_bool(tree.blackboard.get_value("entered", false)).is_true() assert_bool(tree.blackboard.get_value("exited", false)).is_false() - assert_int(tree.tick()).is_equal(BeehaveNode.SUCCESS) + assert_int(tree.tick()).is_equal(BeehaveTreeNode.SUCCESS) assert_bool(tree.blackboard.get_value("entered", false)).is_true() assert_bool(tree.blackboard.get_value("exited", false)).is_true() diff --git a/test/blackboard_test.gd b/test/blackboard_test.gd index 40ad2c2c..af95a416 100644 --- a/test/blackboard_test.gd +++ b/test/blackboard_test.gd @@ -1,44 +1,34 @@ # GdUnit generated TestSuite -class_name BlackboardTest +class_name BeehaveBlackboardTest extends GdUnitTestSuite @warning_ignore("unused_parameter") @warning_ignore("return_value_discarded") -# TestSuite generated from -const __source = "res://addons/beehave/blackboard.gd" - func test_has_value() -> void: - var blackboard = auto_free(load(__source).new()) + var blackboard = auto_free(BeehaveBlackboard.new()) blackboard.set_value("my-key", 123) assert_bool(blackboard.has_value("my-key")).is_true() assert_bool(blackboard.has_value("my-key2")).is_false() func test_erase_value() -> void: - var blackboard = auto_free(load(__source).new()) + var blackboard = auto_free(BeehaveBlackboard.new()) blackboard.set_value("my-key", 123) blackboard.erase_value("my-key") assert_bool(blackboard.has_value("my-key")).is_false() func test_separate_blackboard_erase_value() -> void: - var blackboard = auto_free(load(__source).new()) - blackboard.set_value("my-key", 123, "other-blackboard") - blackboard.erase_value("my-key", "other-blackboard") - assert_bool(blackboard.has_value("my-key", "other-blackboard")).is_false() + var blackboard = auto_free(BeehaveBlackboard.new()) + blackboard.set_value("my-key", 123) + blackboard.erase_value("my-key") + assert_bool(blackboard.has_value("my-key")).is_false() func test_set_value() -> void: - var blackboard = auto_free(load(__source).new()) - blackboard.set_value("my-key", 123) - assert_that(blackboard.get_value("my-key")).is_equal(123) - -func test_separate_blackboard_id_value() -> void: - var blackboard = auto_free(load(__source).new()) + var blackboard = auto_free(BeehaveBlackboard.new()) blackboard.set_value("my-key", 123) - blackboard.set_value("my-key", 234, "other-blackboard") - assert_that(blackboard.get_value("my-key")).is_equal(123) - assert_that(blackboard.get_value("my-key", null, "other-blackboard")).is_equal(234) + assert_that(blackboard.get_value("my-key", null)).is_equal(123) func test_get_default() -> void: - var blackboard = auto_free(load(__source).new()) + var blackboard = auto_free(BeehaveBlackboard.new()) blackboard.set_value("my-key", 123) assert_that(blackboard.get_value("my-key2", 234)).is_equal(234) @@ -48,6 +38,5 @@ func test_blackboard_shared_between_trees() -> void: await runner.simulate_frames(100) - assert_that(scene.blackboard.get_value("custom_value")).is_equal(4) - assert_that(scene.blackboard.get_value("custom_value")).is_equal(4) - assert_that(scene.blackboard.keys().size()).is_equal(3) + assert_that(scene.blackboard.get_value("c", null)).is_equal(4) + assert_that(scene.blackboard.get_size()).is_equal(3) diff --git a/test/conditions/value_reached_condition.gd b/test/conditions/value_reached_condition.gd index 30aeeb57..de515eae 100644 --- a/test/conditions/value_reached_condition.gd +++ b/test/conditions/value_reached_condition.gd @@ -3,7 +3,7 @@ class_name ValueReachedCondition extends ConditionLeaf @export var limit = 2 @export var key = "custom_value" -func tick(_actor: Node, blackboard: Blackboard) -> int: +func tick(_context: BeehaveContext) -> int: if blackboard.get_value(key, 0) >= limit: return SUCCESS else: diff --git a/test/debug/debugger_test.gd b/test/debug/debugger_test.gd deleted file mode 100644 index 8c9d9006..00000000 --- a/test/debug/debugger_test.gd +++ /dev/null @@ -1,21 +0,0 @@ -# GdUnit generated TestSuite -class_name DebuggerTest -extends GdUnitTestSuite -@warning_ignore("unused_parameter") -@warning_ignore("return_value_discarded") - -# TestSuite generated from -const __source = "res://addons/beehave/debug/debugger_tab.gd" -const TestScene = preload("res://test/debug/debugger_test_scene.tscn") - -func create_scene() -> Node2D: - return auto_free(TestScene.instantiate()) - - -func test_debugger_renders_correctly(): - var scene = create_scene() - var runner = scene_runner(scene) - await runner.simulate_frames(20) - runner.set_mouse_pos(Vector2(20, 20)) - runner.simulate_mouse_button_press(1) - await runner.simulate_frames(10) diff --git a/test/debug/debugger_test_scene.gd b/test/debug/debugger_test_scene.gd deleted file mode 100644 index 2722b36e..00000000 --- a/test/debug/debugger_test_scene.gd +++ /dev/null @@ -1,11 +0,0 @@ -extends Node2D - - -@onready var beehave_debugger_tab: BeehaveDebuggerTab = %BeehaveDebuggerTab -@onready var beehave_tree: BeehaveTree = %BeehaveTree - - -func _ready() -> void: - var tree_data:Dictionary = beehave_tree._get_debugger_data(beehave_tree) - beehave_debugger_tab.register_tree(tree_data) - beehave_debugger_tab.start() diff --git a/test/debug/debugger_test_scene.tscn b/test/debug/debugger_test_scene.tscn deleted file mode 100644 index e4a46853..00000000 --- a/test/debug/debugger_test_scene.tscn +++ /dev/null @@ -1,44 +0,0 @@ -[gd_scene load_steps=8 format=3 uid="uid://b73wipsrkaboq"] - -[ext_resource type="Script" path="res://test/debug/debugger_test_scene.gd" id="1_u2x7i"] -[ext_resource type="Script" path="res://addons/beehave/debug/debugger_tab.gd" id="2_3lxjr"] -[ext_resource type="Script" path="res://addons/beehave/nodes/beehave_tree.gd" id="3_v5wys"] -[ext_resource type="Script" path="res://addons/beehave/nodes/composites/sequence.gd" id="4_w0qba"] -[ext_resource type="Script" path="res://addons/beehave/nodes/decorators/succeeder.gd" id="5_4ynoj"] -[ext_resource type="Script" path="res://addons/beehave/nodes/leaves/condition.gd" id="6_kqyiy"] -[ext_resource type="Script" path="res://test/actions/mock_action.gd" id="7_nf6a7"] - -[node name="debugger_test_scene" type="Node2D"] -script = ExtResource("1_u2x7i") - -[node name="BeehaveTree" type="Node" parent="."] -unique_name_in_owner = true -script = ExtResource("3_v5wys") - -[node name="SequenceComposite" type="Node" parent="BeehaveTree"] -script = ExtResource("4_w0qba") - -[node name="AlwaysSucceedDecorator" type="Node" parent="BeehaveTree/SequenceComposite"] -script = ExtResource("5_4ynoj") - -[node name="ConditionLeaf" type="Node" parent="BeehaveTree/SequenceComposite/AlwaysSucceedDecorator"] -script = ExtResource("6_kqyiy") - -[node name="MockAction" type="Node" parent="BeehaveTree/SequenceComposite"] -script = ExtResource("7_nf6a7") - -[node name="CanvasLayer" type="CanvasLayer" parent="."] - -[node name="HBoxContainer" type="HBoxContainer" parent="CanvasLayer"] -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -size_flags_horizontal = 3 - -[node name="BeehaveDebuggerTab" type="PanelContainer" parent="CanvasLayer/HBoxContainer"] -unique_name_in_owner = true -layout_mode = 2 -size_flags_horizontal = 3 -script = ExtResource("2_3lxjr") diff --git a/test/nodes/composites/selector_random_test.gd b/test/nodes/composites/selector_random_test.gd index 93f7d60d..54471618 100644 --- a/test/nodes/composites/selector_random_test.gd +++ b/test/nodes/composites/selector_random_test.gd @@ -7,25 +7,23 @@ extends GdUnitTestSuite # TestSuite generated from const __source = "res://addons/beehave/nodes/composites/selector_random.gd" const __count_up_action = "res://test/actions/count_up_action.gd" -const __blackboard = "res://addons/beehave/blackboard.gd" -const __tree = "res://addons/beehave/nodes/beehave_tree.gd" const RANDOM_SEED = 123 var tree: BeehaveTree var selector: SelectorRandomComposite -var action1: ActionLeaf -var action2: ActionLeaf +var action1: BeehaveAction +var action2: BeehaveAction var actor: Node -var blackboard: Blackboard +var blackboard: BeehaveBlackboard func before_test() -> void: - tree = auto_free(load(__tree).new()) + tree = auto_free(BeehaveTree.new()) selector = auto_free(load(__source).new()) action1 = auto_free(load(__count_up_action).new()) action2 = auto_free(load(__count_up_action).new()) actor = auto_free(Node2D.new()) - blackboard = auto_free(load(__blackboard).new()) + blackboard = auto_free(BeehaveBlackboard.new()) tree.add_child(selector) selector.add_child(action1) @@ -37,43 +35,43 @@ func before_test() -> void: func test_always_executing_first_successful_node() -> void: selector.random_seed = RANDOM_SEED - assert_that(selector.tick(actor, blackboard)).is_equal(BeehaveNode.SUCCESS) - assert_that(selector.tick(actor, blackboard)).is_equal(BeehaveNode.SUCCESS) + assert_that(selector.tick(actor, blackboard)).is_equal(BeehaveTreeNode.SUCCESS) + assert_that(selector.tick(actor, blackboard)).is_equal(BeehaveTreeNode.SUCCESS) assert_that(action1.count).is_equal(1) assert_that(action2.count).is_equal(1) func test_execute_second_when_first_is_failing() -> void: selector.random_seed = RANDOM_SEED - action2.status = BeehaveNode.FAILURE - assert_that(selector.tick(actor, blackboard)).is_equal(BeehaveNode.SUCCESS) - assert_that(selector.tick(actor, blackboard)).is_equal(BeehaveNode.SUCCESS) + action2.status = BeehaveTreeNode.FAILURE + assert_that(selector.tick(actor, blackboard)).is_equal(BeehaveTreeNode.SUCCESS) + assert_that(selector.tick(actor, blackboard)).is_equal(BeehaveTreeNode.SUCCESS) assert_that(action2.count).is_equal(1) assert_that(action1.count).is_equal(2) func test_random_even_execution() -> void: selector.random_seed = RANDOM_SEED - assert_that(selector.tick(actor, blackboard)).is_equal(BeehaveNode.SUCCESS) + assert_that(selector.tick(actor, blackboard)).is_equal(BeehaveTreeNode.SUCCESS) assert_that(action2.count).is_equal(1) - assert_that(selector.tick(actor, blackboard)).is_equal(BeehaveNode.SUCCESS) + assert_that(selector.tick(actor, blackboard)).is_equal(BeehaveTreeNode.SUCCESS) assert_that(action1.count).is_equal(1) func test_return_failure_of_none_is_succeeding() -> void: selector.random_seed = RANDOM_SEED - action1.status = BeehaveNode.FAILURE - action2.status = BeehaveNode.FAILURE - assert_that(selector.tick(actor, blackboard)).is_equal(BeehaveNode.FAILURE) + action1.status = BeehaveTreeNode.FAILURE + action2.status = BeehaveTreeNode.FAILURE + assert_that(selector.tick(actor, blackboard)).is_equal(BeehaveTreeNode.FAILURE) assert_that(action1.count).is_equal(1) assert_that(action2.count).is_equal(1) func test_clear_running_child_after_run() -> void: - action1.status = BeehaveNode.FAILURE - action2.status = BeehaveNode.RUNNING + action1.status = BeehaveTreeNode.FAILURE + action2.status = BeehaveTreeNode.RUNNING tree.tick() assert_that(selector.running_child).is_equal(action2) - action2.status = BeehaveNode.FAILURE + action2.status = BeehaveTreeNode.FAILURE tree.tick() assert_that(selector.running_child).is_equal(null) diff --git a/test/nodes/composites/selector_reactive_test.gd b/test/nodes/composites/selector_reactive_test.gd index fb64df43..20afd7e6 100644 --- a/test/nodes/composites/selector_reactive_test.gd +++ b/test/nodes/composites/selector_reactive_test.gd @@ -8,22 +8,20 @@ extends GdUnitTestSuite # TestSuite generated from const __source = "res://addons/beehave/nodes/composites/selector_reactive.gd" const __count_up_action = "res://test/actions/count_up_action.gd" -const __blackboard = "res://addons/beehave/blackboard.gd" -const __tree = "res://addons/beehave/nodes/beehave_tree.gd" var tree: BeehaveTree var selector: SelectorReactiveComposite -var action1: ActionLeaf -var action2: ActionLeaf +var action1: BeehaveAction +var action2: BeehaveAction func before_test() -> void: - tree = auto_free(load(__tree).new()) + tree = auto_free(BeehaveTree.new()) action1 = auto_free(load(__count_up_action).new()) action2 = auto_free(load(__count_up_action).new()) selector = auto_free(load(__source).new()) var actor = auto_free(Node2D.new()) - var blackboard = auto_free(load(__blackboard).new()) + var blackboard = auto_free(BeehaveBlackboard.new()) tree.add_child(selector) selector.add_child(action1) @@ -37,7 +35,7 @@ func test_always_executing_first_successful_node() -> void: var times_to_run = 2 for i in range(times_to_run): - assert_that(tree.tick()).is_equal(BeehaveNode.SUCCESS) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.SUCCESS) assert_that(action1.count).is_equal(times_to_run) assert_that(action2.count).is_equal(0) @@ -46,86 +44,86 @@ func test_always_executing_first_successful_node() -> void: func test_execute_second_when_first_is_failing() -> void: var times_to_run = 2 - action1.status = BeehaveNode.FAILURE - action2.status = BeehaveNode.SUCCESS + action1.status = BeehaveTreeNode.FAILURE + action2.status = BeehaveTreeNode.SUCCESS for i in range(times_to_run): - assert_that(tree.tick()).is_equal(BeehaveNode.SUCCESS) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.SUCCESS) assert_that(action1.count).is_equal(times_to_run) assert_that(action2.count).is_equal(times_to_run) func test_return_failure_of_none_is_succeeding() -> void: - action1.status = BeehaveNode.FAILURE - action2.status = BeehaveNode.FAILURE + action1.status = BeehaveTreeNode.FAILURE + action2.status = BeehaveTreeNode.FAILURE - assert_that(tree.tick()).is_equal(BeehaveNode.FAILURE) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.FAILURE) assert_that(action1.count).is_equal(1) assert_that(action2.count).is_equal(1) func test_keeps_restarting_child_until_success() -> void: - action1.status = BeehaveNode.FAILURE - action2.status = BeehaveNode.RUNNING + action1.status = BeehaveTreeNode.FAILURE + action2.status = BeehaveTreeNode.RUNNING for i in range(2): - assert_that(tree.tick()).is_equal(BeehaveNode.RUNNING) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.RUNNING) assert_that(action1.count).is_equal(2) assert_that(action2.count).is_equal(2) - action2.status = BeehaveNode.SUCCESS + action2.status = BeehaveTreeNode.SUCCESS - assert_that(tree.tick()).is_equal(BeehaveNode.SUCCESS) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.SUCCESS) assert_that(action1.count).is_equal(3) assert_that(action2.count).is_equal(3) - assert_that(tree.tick()).is_equal(BeehaveNode.SUCCESS) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.SUCCESS) assert_that(action1.count).is_equal(4) assert_that(action2.count).is_equal(4) func test_keeps_restarting_child_until_failure() -> void: - action1.status = BeehaveNode.FAILURE - action2.status = BeehaveNode.RUNNING + action1.status = BeehaveTreeNode.FAILURE + action2.status = BeehaveTreeNode.RUNNING for i in range(2): - assert_that(tree.tick()).is_equal(BeehaveNode.RUNNING) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.RUNNING) assert_that(action1.count).is_equal(2) assert_that(action2.count).is_equal(2) - action2.status = BeehaveNode.FAILURE + action2.status = BeehaveTreeNode.FAILURE - assert_that(tree.tick()).is_equal(BeehaveNode.FAILURE) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.FAILURE) assert_that(action1.count).is_equal(3) assert_that(action2.count).is_equal(3) - assert_that(tree.tick()).is_equal(BeehaveNode.FAILURE) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.FAILURE) assert_that(action1.count).is_equal(4) assert_that(action2.count).is_equal(4) func test_interrupt_second_when_first_is_running() -> void: - action1.status = BeehaveNode.FAILURE - action2.status = BeehaveNode.RUNNING - assert_that(tree.tick()).is_equal(BeehaveNode.RUNNING) + action1.status = BeehaveTreeNode.FAILURE + action2.status = BeehaveTreeNode.RUNNING + assert_that(tree.tick()).is_equal(BeehaveTreeNode.RUNNING) assert_that(action1.count).is_equal(1) assert_that(action2.count).is_equal(1) - action1.status = BeehaveNode.RUNNING - assert_that(tree.tick()).is_equal(BeehaveNode.RUNNING) + action1.status = BeehaveTreeNode.RUNNING + assert_that(tree.tick()).is_equal(BeehaveTreeNode.RUNNING) assert_that(action1.count).is_equal(2) assert_that(action2.count).is_equal(0) func test_clear_running_child_after_run() -> void: - action1.status = BeehaveNode.FAILURE - action2.status = BeehaveNode.RUNNING + action1.status = BeehaveTreeNode.FAILURE + action2.status = BeehaveTreeNode.RUNNING tree.tick() assert_that(selector.running_child).is_equal(action2) - action2.status = BeehaveNode.FAILURE + action2.status = BeehaveTreeNode.FAILURE tree.tick() assert_that(selector.running_child).is_equal(null) diff --git a/test/nodes/composites/selector_test.gd b/test/nodes/composites/selector_test.gd index 4eaf2477..ee811d21 100644 --- a/test/nodes/composites/selector_test.gd +++ b/test/nodes/composites/selector_test.gd @@ -7,25 +7,23 @@ extends GdUnitTestSuite # TestSuite generated from const __source = "res://addons/beehave/nodes/composites/selector.gd" const __count_up_action = "res://test/actions/count_up_action.gd" -const __blackboard = "res://addons/beehave/blackboard.gd" -const __tree = "res://addons/beehave/nodes/beehave_tree.gd" const __selector_reactive = "res://addons/beehave/nodes/composites/selector_reactive.gd" var tree: BeehaveTree var selector: SelectorComposite -var action1: ActionLeaf -var action2: ActionLeaf +var action1: BeehaveAction +var action2: BeehaveAction var actor: Node -var blackboard: Blackboard +var blackboard: BeehaveBlackboard func before_test() -> void: - tree = auto_free(load(__tree).new()) + tree = auto_free(BeehaveTree.new()) selector = auto_free(load(__source).new()) action1 = auto_free(load(__count_up_action).new()) action2 = auto_free(load(__count_up_action).new()) actor = auto_free(Node2D.new()) - blackboard = auto_free(load(__blackboard).new()) + blackboard = auto_free(BeehaveBlackboard.new()) tree.add_child(selector) selector.add_child(action1) @@ -36,69 +34,69 @@ func before_test() -> void: func test_always_executing_first_successful_node() -> void: - assert_that(selector.tick(actor, blackboard)).is_equal(BeehaveNode.SUCCESS) - assert_that(selector.tick(actor, blackboard)).is_equal(BeehaveNode.SUCCESS) + assert_that(selector.tick(actor, blackboard)).is_equal(BeehaveTreeNode.SUCCESS) + assert_that(selector.tick(actor, blackboard)).is_equal(BeehaveTreeNode.SUCCESS) assert_that(action1.count).is_equal(2) assert_that(action2.count).is_equal(0) func test_execute_second_when_first_is_failing() -> void: - action1.status = BeehaveNode.FAILURE - assert_that(selector.tick(actor, blackboard)).is_equal(BeehaveNode.SUCCESS) - assert_that(selector.tick(actor, blackboard)).is_equal(BeehaveNode.SUCCESS) + action1.status = BeehaveTreeNode.FAILURE + assert_that(selector.tick(actor, blackboard)).is_equal(BeehaveTreeNode.SUCCESS) + assert_that(selector.tick(actor, blackboard)).is_equal(BeehaveTreeNode.SUCCESS) assert_that(action1.count).is_equal(1) assert_that(action2.count).is_equal(2) func test_return_failure_of_none_is_succeeding() -> void: - action1.status = BeehaveNode.FAILURE - action2.status = BeehaveNode.FAILURE - assert_that(selector.tick(actor, blackboard)).is_equal(BeehaveNode.FAILURE) + action1.status = BeehaveTreeNode.FAILURE + action2.status = BeehaveTreeNode.FAILURE + assert_that(selector.tick(actor, blackboard)).is_equal(BeehaveTreeNode.FAILURE) assert_that(action1.count).is_equal(1) assert_that(action2.count).is_equal(1) func test_not_interrupt_second_when_first_is_succeeding() -> void: - action1.status = BeehaveNode.FAILURE - action2.status = BeehaveNode.RUNNING - assert_that(selector.tick(actor, blackboard)).is_equal(BeehaveNode.RUNNING) + action1.status = BeehaveTreeNode.FAILURE + action2.status = BeehaveTreeNode.RUNNING + assert_that(selector.tick(actor, blackboard)).is_equal(BeehaveTreeNode.RUNNING) assert_that(action1.count).is_equal(1) assert_that(action2.count).is_equal(1) - action1.status = BeehaveNode.SUCCESS - assert_that(selector.tick(actor, blackboard)).is_equal(BeehaveNode.RUNNING) + action1.status = BeehaveTreeNode.SUCCESS + assert_that(selector.tick(actor, blackboard)).is_equal(BeehaveTreeNode.RUNNING) assert_that(action1.count).is_equal(1) assert_that(action2.count).is_equal(2) func test_not_interrupt_second_when_first_is_running() -> void: - action1.status = BeehaveNode.FAILURE - action2.status = BeehaveNode.RUNNING - assert_that(selector.tick(actor, blackboard)).is_equal(BeehaveNode.RUNNING) + action1.status = BeehaveTreeNode.FAILURE + action2.status = BeehaveTreeNode.RUNNING + assert_that(selector.tick(actor, blackboard)).is_equal(BeehaveTreeNode.RUNNING) assert_that(action1.count).is_equal(1) assert_that(action2.count).is_equal(1) - action1.status = BeehaveNode.RUNNING - assert_that(selector.tick(actor, blackboard)).is_equal(BeehaveNode.RUNNING) + action1.status = BeehaveTreeNode.RUNNING + assert_that(selector.tick(actor, blackboard)).is_equal(BeehaveTreeNode.RUNNING) assert_that(action1.count).is_equal(1) assert_that(action2.count).is_equal(2) func test_tick_again_when_child_returns_running() -> void: - action1.status = BeehaveNode.FAILURE - action2.status = BeehaveNode.RUNNING - assert_that(selector.tick(actor, blackboard)).is_equal(BeehaveNode.RUNNING) - assert_that(selector.tick(actor, blackboard)).is_equal(BeehaveNode.RUNNING) + action1.status = BeehaveTreeNode.FAILURE + action2.status = BeehaveTreeNode.RUNNING + assert_that(selector.tick(actor, blackboard)).is_equal(BeehaveTreeNode.RUNNING) + assert_that(selector.tick(actor, blackboard)).is_equal(BeehaveTreeNode.RUNNING) assert_that(action1.count).is_equal(1) assert_that(action2.count).is_equal(2) func test_clear_running_child_after_run() -> void: - action1.status = BeehaveNode.FAILURE - action2.status = BeehaveNode.RUNNING + action1.status = BeehaveTreeNode.FAILURE + action2.status = BeehaveTreeNode.RUNNING tree.tick() assert_that(selector.running_child).is_equal(action2) - action2.status = BeehaveNode.FAILURE + action2.status = BeehaveTreeNode.FAILURE tree.tick() assert_that(selector.running_child).is_equal(null) @@ -107,17 +105,17 @@ func test_not_interrupt_first_after_finished() -> void: var action3 = auto_free(load(__count_up_action).new()) selector.add_child(action3) - action1.status = BeehaveNode.RUNNING - action2.status = BeehaveNode.FAILURE - action3.status = BeehaveNode.RUNNING + action1.status = BeehaveTreeNode.RUNNING + action2.status = BeehaveTreeNode.FAILURE + action3.status = BeehaveTreeNode.RUNNING - assert_that(selector.tick(actor, blackboard)).is_equal(BeehaveNode.RUNNING) + assert_that(selector.tick(actor, blackboard)).is_equal(BeehaveTreeNode.RUNNING) assert_that(action1.count).is_equal(1) assert_that(action2.count).is_equal(0) assert_that(action3.count).is_equal(0) - action1.status = BeehaveNode.FAILURE - assert_that(selector.tick(actor, blackboard)).is_equal(BeehaveNode.RUNNING) + action1.status = BeehaveTreeNode.FAILURE + assert_that(selector.tick(actor, blackboard)).is_equal(BeehaveTreeNode.RUNNING) assert_that(action1.count).is_equal(2) assert_that(action2.count).is_equal(1) assert_that(action3.count).is_equal(1) @@ -134,15 +132,15 @@ func test_interrupt_when_nested() -> void: selector_reactive.add_child(fake_condition) selector_reactive.add_child(selector) - fake_condition.status = BeehaveNode.FAILURE - action1.status = BeehaveNode.RUNNING + fake_condition.status = BeehaveTreeNode.FAILURE + action1.status = BeehaveTreeNode.RUNNING - assert_that(tree.tick()).is_equal(BeehaveNode.RUNNING) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.RUNNING) assert_that(action1.count).is_equal(1) assert_that(action2.count).is_equal(0) - fake_condition.status = BeehaveNode.SUCCESS - assert_that(tree.tick()).is_equal(BeehaveNode.SUCCESS) + fake_condition.status = BeehaveTreeNode.SUCCESS + assert_that(tree.tick()).is_equal(BeehaveTreeNode.SUCCESS) assert_that(action1.count).is_equal(0) assert_that(action2.count).is_equal(0) diff --git a/test/nodes/composites/sequence_random_test.gd b/test/nodes/composites/sequence_random_test.gd index 5a716922..b722bfea 100644 --- a/test/nodes/composites/sequence_random_test.gd +++ b/test/nodes/composites/sequence_random_test.gd @@ -8,18 +8,16 @@ extends GdUnitTestSuite # TestSuite generated from const __source = "res://addons/beehave/nodes/composites/sequence_random.gd" const __count_up_action = "res://test/actions/count_up_action.gd" -const __blackboard = "res://addons/beehave/blackboard.gd" -const __tree = "res://addons/beehave/nodes/beehave_tree.gd" const RANDOM_SEED = 123 var tree: BeehaveTree var sequence: SequenceRandomComposite -var action1: ActionLeaf -var action2: ActionLeaf +var action1: BeehaveAction +var action2: BeehaveAction func before_test() -> void: - tree = auto_free(load(__tree).new()) + tree = auto_free(BeehaveTree.new()) action1 = auto_free(load(__count_up_action).new()) action1.name = 'Action 1' action2 = auto_free(load(__count_up_action).new()) @@ -27,7 +25,7 @@ func before_test() -> void: sequence = auto_free(load(__source).new()) sequence.random_seed = RANDOM_SEED var actor = auto_free(Node2D.new()) - var blackboard = auto_free(load(__blackboard).new()) + var blackboard = auto_free(BeehaveBlackboard.new()) tree.add_child(sequence) sequence.add_child(action1) @@ -41,27 +39,27 @@ func test_always_executing_first_successful_node() -> void: var times_to_run = 2 for i in range(times_to_run): - assert_that(tree.tick()).is_equal(BeehaveNode.SUCCESS) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.SUCCESS) assert_that(action1.count).is_equal(times_to_run) assert_that(action2.count).is_equal(times_to_run) func test_execute_second_when_first_is_failing() -> void: var times_to_run = 2 - action1.status = BeehaveNode.FAILURE + action1.status = BeehaveTreeNode.FAILURE for i in range(times_to_run): - assert_that(tree.tick()).is_equal(BeehaveNode.FAILURE) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.FAILURE) assert_that(action1.count).is_equal(2) assert_that(action2.count).is_equal(1) func test_random_even_execution() -> void: - assert_that(tree.tick()).is_equal(BeehaveNode.SUCCESS) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.SUCCESS) assert_that(action1.count).is_equal(1) - assert_that(tree.tick()).is_equal(BeehaveNode.SUCCESS) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.SUCCESS) assert_that(action2.count).is_equal(2) @@ -71,12 +69,12 @@ func test_weighted_random_sampling() -> void: assert_dict(sequence._weights).contains_key_value(action1.name, 2) assert_dict(sequence._weights).contains_key_value(action2.name, 1) - action1.status = BeehaveNode.RUNNING - action2.status = BeehaveNode.RUNNING + action1.status = BeehaveTreeNode.RUNNING + action2.status = BeehaveTreeNode.RUNNING assert_array(sequence._children_bag).is_empty() - assert_that(tree.tick()).is_equal(BeehaveNode.RUNNING) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.RUNNING) # Children are in reverse order; aka action1 will run first. assert_array(sequence._children_bag)\ @@ -86,9 +84,9 @@ func test_weighted_random_sampling() -> void: assert_that(action1.count).is_equal(1) assert_that(action2.count).is_equal(0) - action1.status = BeehaveNode.SUCCESS + action1.status = BeehaveTreeNode.SUCCESS - assert_that(tree.tick()).is_equal(BeehaveNode.RUNNING) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.RUNNING) assert_that(action1.count).is_equal(2) assert_that(action2.count).is_equal(1) @@ -97,20 +95,20 @@ func test_weighted_random_sampling() -> void: func test_return_failure_of_none_is_succeeding() -> void: - action1.status = BeehaveNode.FAILURE - action2.status = BeehaveNode.FAILURE + action1.status = BeehaveTreeNode.FAILURE + action2.status = BeehaveTreeNode.FAILURE - assert_that(tree.tick()).is_equal(BeehaveNode.FAILURE) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.FAILURE) assert_that(action1.count).is_equal(0) assert_that(action2.count).is_equal(1) func test_clear_running_child_after_run() -> void: - action1.status = BeehaveNode.SUCCESS - action2.status = BeehaveNode.RUNNING + action1.status = BeehaveTreeNode.SUCCESS + action2.status = BeehaveTreeNode.RUNNING tree.tick() assert_that(sequence.running_child).is_equal(action2) - action2.status = BeehaveNode.SUCCESS + action2.status = BeehaveTreeNode.SUCCESS tree.tick() assert_that(sequence.running_child).is_equal(null) diff --git a/test/nodes/composites/sequence_reactive_test.gd b/test/nodes/composites/sequence_reactive_test.gd index 5b3f48d0..c3814d5c 100644 --- a/test/nodes/composites/sequence_reactive_test.gd +++ b/test/nodes/composites/sequence_reactive_test.gd @@ -8,24 +8,22 @@ extends GdUnitTestSuite # TestSuite generated from const __source = "res://addons/beehave/nodes/composites/sequence_reactive.gd" const __count_up_action = "res://test/actions/count_up_action.gd" -const __blackboard = "res://addons/beehave/blackboard.gd" -const __tree = "res://addons/beehave/nodes/beehave_tree.gd" var tree: BeehaveTree -var action1: ActionLeaf -var action2: ActionLeaf +var action1: BeehaveAction +var action2: BeehaveAction var actor: Node var sequence: SequenceReactiveComposite -var blackboard: Blackboard +var blackboard: BeehaveBlackboard func before_test() -> void: - tree = auto_free(load(__tree).new()) + tree = auto_free(BeehaveTree.new()) action1 = auto_free(load(__count_up_action).new()) action2 = auto_free(load(__count_up_action).new()) sequence = auto_free(load(__source).new()) actor = auto_free(Node2D.new()) - blackboard = auto_free(load(__blackboard).new()) + blackboard = auto_free(BeehaveBlackboard.new()) tree.add_child(sequence) sequence.add_child(action1) @@ -39,7 +37,7 @@ func test_always_exexuting_all_successful_nodes() -> void: var times_to_run = 2 for i in range(times_to_run): - assert_that(tree.tick()).is_equal(BeehaveNode.SUCCESS) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.SUCCESS) assert_that(action1.count).is_equal(times_to_run) assert_that(action2.count).is_equal(times_to_run) @@ -47,90 +45,90 @@ func test_always_exexuting_all_successful_nodes() -> void: func test_never_execute_second_when_first_is_failing() -> void: var times_to_run = 2 - action1.status = BeehaveNode.FAILURE + action1.status = BeehaveTreeNode.FAILURE for i in range(times_to_run): - assert_that(tree.tick()).is_equal(BeehaveNode.FAILURE) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.FAILURE) assert_that(action1.count).is_equal(times_to_run) assert_that(action2.count).is_equal(0) func test_keeps_running_child_until_success() -> void: - action1.status = BeehaveNode.SUCCESS - action2.status = BeehaveNode.RUNNING + action1.status = BeehaveTreeNode.SUCCESS + action2.status = BeehaveTreeNode.RUNNING for i in range(2): - assert_that(tree.tick()).is_equal(BeehaveNode.RUNNING) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.RUNNING) assert_that(action1.count).is_equal(2) assert_that(action2.count).is_equal(2) - action2.status = BeehaveNode.SUCCESS + action2.status = BeehaveTreeNode.SUCCESS - assert_that(tree.tick()).is_equal(BeehaveNode.SUCCESS) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.SUCCESS) assert_that(action1.count).is_equal(3) assert_that(action2.count).is_equal(3) - assert_that(tree.tick()).is_equal(BeehaveNode.SUCCESS) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.SUCCESS) assert_that(action1.count).is_equal(4) assert_that(action2.count).is_equal(4) func test_keeps_running_child_until_failure() -> void: - action1.status = BeehaveNode.SUCCESS - action2.status = BeehaveNode.RUNNING + action1.status = BeehaveTreeNode.SUCCESS + action2.status = BeehaveTreeNode.RUNNING for i in range(2): - assert_that(tree.tick()).is_equal(BeehaveNode.RUNNING) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.RUNNING) assert_that(action1.count).is_equal(2) assert_that(action2.count).is_equal(2) - action2.status = BeehaveNode.FAILURE + action2.status = BeehaveTreeNode.FAILURE - assert_that(tree.tick()).is_equal(BeehaveNode.FAILURE) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.FAILURE) assert_that(action1.count).is_equal(3) assert_that(action2.count).is_equal(0) - assert_that(tree.tick()).is_equal(BeehaveNode.FAILURE) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.FAILURE) assert_that(action1.count).is_equal(4) assert_that(action2.count).is_equal(1) func test_restart_when_child_returns_failure() -> void: - action1.status = BeehaveNode.SUCCESS - action2.status = BeehaveNode.FAILURE - assert_that(sequence.tick(actor, blackboard)).is_equal(BeehaveNode.FAILURE) - assert_that(sequence.tick(actor, blackboard)).is_equal(BeehaveNode.FAILURE) + action1.status = BeehaveTreeNode.SUCCESS + action2.status = BeehaveTreeNode.FAILURE + assert_that(sequence.tick(actor, blackboard)).is_equal(BeehaveTreeNode.FAILURE) + assert_that(sequence.tick(actor, blackboard)).is_equal(BeehaveTreeNode.FAILURE) assert_that(action1.count).is_equal(2) assert_that(action2.count).is_equal(2) func test_restart_again_when_child_returns_running() -> void: - action1.status = BeehaveNode.SUCCESS - action2.status = BeehaveNode.RUNNING - assert_that(sequence.tick(actor, blackboard)).is_equal(BeehaveNode.RUNNING) - assert_that(sequence.tick(actor, blackboard)).is_equal(BeehaveNode.RUNNING) + action1.status = BeehaveTreeNode.SUCCESS + action2.status = BeehaveTreeNode.RUNNING + assert_that(sequence.tick(actor, blackboard)).is_equal(BeehaveTreeNode.RUNNING) + assert_that(sequence.tick(actor, blackboard)).is_equal(BeehaveTreeNode.RUNNING) assert_that(action1.count).is_equal(2) assert_that(action2.count).is_equal(2) func test_interrupt_second_when_first_is_running() -> void: - action1.status = BeehaveNode.SUCCESS - action2.status = BeehaveNode.RUNNING - assert_that(tree.tick()).is_equal(BeehaveNode.RUNNING) + action1.status = BeehaveTreeNode.SUCCESS + action2.status = BeehaveTreeNode.RUNNING + assert_that(tree.tick()).is_equal(BeehaveTreeNode.RUNNING) assert_that(action1.count).is_equal(1) assert_that(action2.count).is_equal(1) - action1.status = BeehaveNode.RUNNING - assert_that(tree.tick()).is_equal(BeehaveNode.RUNNING) + action1.status = BeehaveTreeNode.RUNNING + assert_that(tree.tick()).is_equal(BeehaveTreeNode.RUNNING) assert_that(action1.count).is_equal(2) assert_that(action2.count).is_equal(0) func test_clear_running_child_after_run() -> void: - action1.status = BeehaveNode.SUCCESS - action2.status = BeehaveNode.RUNNING + action1.status = BeehaveTreeNode.SUCCESS + action2.status = BeehaveTreeNode.RUNNING tree.tick() assert_that(sequence.running_child).is_equal(action2) - action2.status = BeehaveNode.SUCCESS + action2.status = BeehaveTreeNode.SUCCESS tree.tick() assert_that(sequence.running_child).is_equal(null) diff --git a/test/nodes/composites/sequence_star_test.gd b/test/nodes/composites/sequence_star_test.gd index cadd6cc7..a3013643 100644 --- a/test/nodes/composites/sequence_star_test.gd +++ b/test/nodes/composites/sequence_star_test.gd @@ -8,24 +8,22 @@ extends GdUnitTestSuite # TestSuite generated from const __source = "res://addons/beehave/nodes/composites/sequence_star.gd" const __count_up_action = "res://test/actions/count_up_action.gd" -const __blackboard = "res://addons/beehave/blackboard.gd" -const __tree = "res://addons/beehave/nodes/beehave_tree.gd" var tree: BeehaveTree -var action1: ActionLeaf -var action2: ActionLeaf +var action1: BeehaveAction +var action2: BeehaveAction var actor: Node -var blackboard: Blackboard +var blackboard: BeehaveBlackboard var sequence: SequenceStarComposite func before_test() -> void: - tree = auto_free(load(__tree).new()) + tree = auto_free(BeehaveTree.new()) action1 = auto_free(load(__count_up_action).new()) action2 = auto_free(load(__count_up_action).new()) sequence = auto_free(load(__source).new()) actor = auto_free(Node2D.new()) - blackboard = auto_free(load(__blackboard).new()) + blackboard = auto_free(BeehaveBlackboard.new()) tree.add_child(sequence) sequence.add_child(action1) @@ -39,7 +37,7 @@ func test_always_exexuting_all_successful_nodes() -> void: var times_to_run = 2 for i in range(times_to_run): - assert_that(tree.tick()).is_equal(BeehaveNode.SUCCESS) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.SUCCESS) assert_that(action1.count).is_equal(times_to_run) assert_that(action2.count).is_equal(times_to_run) @@ -47,82 +45,82 @@ func test_always_exexuting_all_successful_nodes() -> void: func test_never_execute_second_when_first_is_failing() -> void: var times_to_run = 2 - action1.status = BeehaveNode.FAILURE + action1.status = BeehaveTreeNode.FAILURE for i in range(times_to_run): - assert_that(tree.tick()).is_equal(BeehaveNode.FAILURE) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.FAILURE) assert_that(action1.count).is_equal(times_to_run) assert_that(action2.count).is_equal(0) func test_keeps_running_child_until_success() -> void: - action1.status = BeehaveNode.SUCCESS - action2.status = BeehaveNode.RUNNING + action1.status = BeehaveTreeNode.SUCCESS + action2.status = BeehaveTreeNode.RUNNING for i in range(2): - assert_that(tree.tick()).is_equal(BeehaveNode.RUNNING) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.RUNNING) assert_that(action1.count).is_equal(1) assert_that(action2.count).is_equal(2) - action2.status = BeehaveNode.SUCCESS + action2.status = BeehaveTreeNode.SUCCESS - assert_that(tree.tick()).is_equal(BeehaveNode.SUCCESS) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.SUCCESS) assert_that(action1.count).is_equal(1) assert_that(action2.count).is_equal(3) - assert_that(tree.tick()).is_equal(BeehaveNode.SUCCESS) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.SUCCESS) assert_that(action1.count).is_equal(2) assert_that(action2.count).is_equal(4) func test_keeps_running_child_until_failure() -> void: - action1.status = BeehaveNode.SUCCESS - action2.status = BeehaveNode.RUNNING + action1.status = BeehaveTreeNode.SUCCESS + action2.status = BeehaveTreeNode.RUNNING for i in range(2): - assert_that(tree.tick()).is_equal(BeehaveNode.RUNNING) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.RUNNING) assert_that(action1.count).is_equal(1) assert_that(action2.count).is_equal(2) - action2.status = BeehaveNode.FAILURE + action2.status = BeehaveTreeNode.FAILURE - assert_that(tree.tick()).is_equal(BeehaveNode.FAILURE) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.FAILURE) assert_that(action1.count).is_equal(1) # action2 will reset as it failed assert_that(action2.count).is_equal(0) - assert_that(tree.tick()).is_equal(BeehaveNode.FAILURE) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.FAILURE) assert_that(action1.count).is_equal(1) # action2 has reset previously but sequence star will tick again assert_that(action2.count).is_equal(1) func test_tick_again_when_child_returns_failure() -> void: - action1.status = BeehaveNode.SUCCESS - action2.status = BeehaveNode.FAILURE - assert_that(sequence.tick(actor, blackboard)).is_equal(BeehaveNode.FAILURE) - assert_that(sequence.tick(actor, blackboard)).is_equal(BeehaveNode.FAILURE) + action1.status = BeehaveTreeNode.SUCCESS + action2.status = BeehaveTreeNode.FAILURE + assert_that(sequence.tick(actor, blackboard)).is_equal(BeehaveTreeNode.FAILURE) + assert_that(sequence.tick(actor, blackboard)).is_equal(BeehaveTreeNode.FAILURE) assert_that(action1.count).is_equal(1) assert_that(action2.count).is_equal(2) func test_tick_again_when_child_returns_running() -> void: - action1.status = BeehaveNode.SUCCESS - action2.status = BeehaveNode.RUNNING - assert_that(sequence.tick(actor, blackboard)).is_equal(BeehaveNode.RUNNING) - assert_that(sequence.tick(actor, blackboard)).is_equal(BeehaveNode.RUNNING) + action1.status = BeehaveTreeNode.SUCCESS + action2.status = BeehaveTreeNode.RUNNING + assert_that(sequence.tick(actor, blackboard)).is_equal(BeehaveTreeNode.RUNNING) + assert_that(sequence.tick(actor, blackboard)).is_equal(BeehaveTreeNode.RUNNING) assert_that(action1.count).is_equal(1) assert_that(action2.count).is_equal(2) func test_clear_running_child_after_run() -> void: - action1.status = BeehaveNode.SUCCESS - action2.status = BeehaveNode.RUNNING + action1.status = BeehaveTreeNode.SUCCESS + action2.status = BeehaveTreeNode.RUNNING tree.tick() assert_that(sequence.running_child).is_equal(action2) - action2.status = BeehaveNode.SUCCESS + action2.status = BeehaveTreeNode.SUCCESS tree.tick() assert_that(sequence.running_child).is_equal(null) diff --git a/test/nodes/composites/sequence_test.gd b/test/nodes/composites/sequence_test.gd index a4825a98..2a420486 100644 --- a/test/nodes/composites/sequence_test.gd +++ b/test/nodes/composites/sequence_test.gd @@ -7,24 +7,22 @@ extends GdUnitTestSuite # TestSuite generated from const __source = "res://addons/beehave/nodes/composites/sequence.gd" const __count_up_action = "res://test/actions/count_up_action.gd" -const __blackboard = "res://addons/beehave/blackboard.gd" -const __tree = "res://addons/beehave/nodes/beehave_tree.gd" var tree: BeehaveTree var sequence: SequenceComposite -var action1: ActionLeaf -var action2: ActionLeaf +var action1: BeehaveAction +var action2: BeehaveAction var actor: Node -var blackboard: Blackboard +var blackboard: BeehaveBlackboard func before_test() -> void: - tree = auto_free(load(__tree).new()) + tree = auto_free(BeehaveTree.new()) sequence = auto_free(load(__source).new()) action1 = auto_free(load(__count_up_action).new()) action2 = auto_free(load(__count_up_action).new()) actor = auto_free(Node2D.new()) - blackboard = auto_free(load(__blackboard).new()) + blackboard = auto_free(BeehaveBlackboard.new()) tree.add_child(sequence) sequence.add_child(action1) @@ -35,69 +33,69 @@ func before_test() -> void: func test_always_executing_all_successful_nodes() -> void: - assert_that(sequence.tick(actor, blackboard)).is_equal(BeehaveNode.SUCCESS) - assert_that(sequence.tick(actor, blackboard)).is_equal(BeehaveNode.SUCCESS) + assert_that(sequence.tick(actor, blackboard)).is_equal(BeehaveTreeNode.SUCCESS) + assert_that(sequence.tick(actor, blackboard)).is_equal(BeehaveTreeNode.SUCCESS) assert_that(action1.count).is_equal(2) assert_that(action2.count).is_equal(2) func test_never_execute_second_when_first_is_failing() -> void: - action1.status = BeehaveNode.FAILURE - assert_that(sequence.tick(actor, blackboard)).is_equal(BeehaveNode.FAILURE) - assert_that(sequence.tick(actor, blackboard)).is_equal(BeehaveNode.FAILURE) + action1.status = BeehaveTreeNode.FAILURE + assert_that(sequence.tick(actor, blackboard)).is_equal(BeehaveTreeNode.FAILURE) + assert_that(sequence.tick(actor, blackboard)).is_equal(BeehaveTreeNode.FAILURE) assert_that(action1.count).is_equal(2) assert_that(action2.count).is_equal(0) func test_not_interrupt_second_when_first_is_failing() -> void: - action1.status = BeehaveNode.SUCCESS - action2.status = BeehaveNode.RUNNING - assert_that(sequence.tick(actor, blackboard)).is_equal(BeehaveNode.RUNNING) + action1.status = BeehaveTreeNode.SUCCESS + action2.status = BeehaveTreeNode.RUNNING + assert_that(sequence.tick(actor, blackboard)).is_equal(BeehaveTreeNode.RUNNING) assert_that(action1.count).is_equal(1) assert_that(action2.count).is_equal(1) - action1.status = BeehaveNode.FAILURE - assert_that(sequence.tick(actor, blackboard)).is_equal(BeehaveNode.RUNNING) + action1.status = BeehaveTreeNode.FAILURE + assert_that(sequence.tick(actor, blackboard)).is_equal(BeehaveTreeNode.RUNNING) assert_that(action1.count).is_equal(1) assert_that(action2.count).is_equal(2) func test_not_interrupting_second_when_first_is_running() -> void: - action1.status = BeehaveNode.SUCCESS - action2.status = BeehaveNode.RUNNING - assert_that(sequence.tick(actor, blackboard)).is_equal(BeehaveNode.RUNNING) + action1.status = BeehaveTreeNode.SUCCESS + action2.status = BeehaveTreeNode.RUNNING + assert_that(sequence.tick(actor, blackboard)).is_equal(BeehaveTreeNode.RUNNING) assert_that(action1.count).is_equal(1) assert_that(action2.count).is_equal(1) - action1.status = BeehaveNode.RUNNING - assert_that(sequence.tick(actor, blackboard)).is_equal(BeehaveNode.RUNNING) + action1.status = BeehaveTreeNode.RUNNING + assert_that(sequence.tick(actor, blackboard)).is_equal(BeehaveTreeNode.RUNNING) assert_that(action1.count).is_equal(1) assert_that(action2.count).is_equal(2) func test_restart_when_child_returns_failure() -> void: - action1.status = BeehaveNode.SUCCESS - action2.status = BeehaveNode.FAILURE - assert_that(sequence.tick(actor, blackboard)).is_equal(BeehaveNode.FAILURE) - assert_that(sequence.tick(actor, blackboard)).is_equal(BeehaveNode.FAILURE) + action1.status = BeehaveTreeNode.SUCCESS + action2.status = BeehaveTreeNode.FAILURE + assert_that(sequence.tick(actor, blackboard)).is_equal(BeehaveTreeNode.FAILURE) + assert_that(sequence.tick(actor, blackboard)).is_equal(BeehaveTreeNode.FAILURE) assert_that(action1.count).is_equal(2) assert_that(action2.count).is_equal(2) func test_tick_again_when_child_returns_running() -> void: - action1.status = BeehaveNode.SUCCESS - action2.status = BeehaveNode.RUNNING - assert_that(sequence.tick(actor, blackboard)).is_equal(BeehaveNode.RUNNING) - assert_that(sequence.tick(actor, blackboard)).is_equal(BeehaveNode.RUNNING) + action1.status = BeehaveTreeNode.SUCCESS + action2.status = BeehaveTreeNode.RUNNING + assert_that(sequence.tick(actor, blackboard)).is_equal(BeehaveTreeNode.RUNNING) + assert_that(sequence.tick(actor, blackboard)).is_equal(BeehaveTreeNode.RUNNING) assert_that(action1.count).is_equal(1) assert_that(action2.count).is_equal(2) func test_clear_running_child_after_run() -> void: - action1.status = BeehaveNode.SUCCESS - action2.status = BeehaveNode.RUNNING + action1.status = BeehaveTreeNode.SUCCESS + action2.status = BeehaveTreeNode.RUNNING tree.tick() assert_that(sequence.running_child).is_equal(action2) - action2.status = BeehaveNode.SUCCESS + action2.status = BeehaveTreeNode.SUCCESS tree.tick() assert_that(sequence.running_child).is_equal(null) @@ -106,17 +104,17 @@ func test_not_interrupt_first_after_finished() -> void: var action3 = auto_free(load(__count_up_action).new()) sequence.add_child(action3) - action1.status = BeehaveNode.RUNNING - action2.status = BeehaveNode.SUCCESS - action3.status = BeehaveNode.RUNNING + action1.status = BeehaveTreeNode.RUNNING + action2.status = BeehaveTreeNode.SUCCESS + action3.status = BeehaveTreeNode.RUNNING - assert_that(sequence.tick(actor, blackboard)).is_equal(BeehaveNode.RUNNING) + assert_that(sequence.tick(actor, blackboard)).is_equal(BeehaveTreeNode.RUNNING) assert_that(action1.count).is_equal(1) assert_that(action2.count).is_equal(0) assert_that(action3.count).is_equal(0) - action1.status = BeehaveNode.SUCCESS - assert_that(sequence.tick(actor, blackboard)).is_equal(BeehaveNode.RUNNING) + action1.status = BeehaveTreeNode.SUCCESS + assert_that(sequence.tick(actor, blackboard)).is_equal(BeehaveTreeNode.RUNNING) assert_that(action1.count).is_equal(2) assert_that(action2.count).is_equal(1) assert_that(action3.count).is_equal(1) diff --git a/test/nodes/decorators/cooldown_test.gd b/test/nodes/decorators/cooldown_test.gd index 07d64c5c..81c034c0 100644 --- a/test/nodes/decorators/cooldown_test.gd +++ b/test/nodes/decorators/cooldown_test.gd @@ -5,38 +5,35 @@ extends GdUnitTestSuite @warning_ignore('return_value_discarded') # TestSuite generated from -const __source = 'res://addons/beehave/nodes/decorators/cooldown.gd' const __action = "res://test/actions/count_up_action.gd" -const __tree = "res://addons/beehave/nodes/beehave_tree.gd" -const __blackboard = "res://addons/beehave/blackboard.gd" var tree: BeehaveTree -var action: ActionLeaf -var cooldown: CooldownDecorator -var runner:GdUnitSceneRunner +var action: BeehaveAction +var cooldown: BeehaveCooldown func before_test() -> void: - tree = auto_free(load(__tree).new()) + tree = auto_free(BeehaveTree.new()) + tree.process_thread = BeehaveTree.IDLE action = auto_free(load(__action).new()) - cooldown = auto_free(load(__source).new()) + cooldown = auto_free(BeehaveCooldown.new()) var actor = auto_free(Node2D.new()) - var blackboard = auto_free(load(__blackboard).new()) + var blackboard = auto_free(BeehaveBlackboard.new()) tree.add_child(cooldown) cooldown.add_child(action) tree.actor = actor tree.blackboard = blackboard - runner = scene_runner(tree) func test_running_then_fail() -> void: cooldown.wait_time = 1.0 - action.status = BeehaveNode.RUNNING - assert_that(tree.tick()).is_equal(BeehaveNode.RUNNING) - action.status = BeehaveNode.SUCCESS - assert_that(tree.tick()).is_equal(BeehaveNode.SUCCESS) - action.status = BeehaveNode.RUNNING - assert_that(tree.tick()).is_equal(BeehaveNode.FAILURE) - await runner.simulate_frames(1, 2000) - assert_that(tree.tick()).is_equal(BeehaveNode.RUNNING) + action.status = BeehaveTreeNode.RUNNING + assert_that(tree.tick()).is_equal(BeehaveTreeNode.RUNNING) + action.status = BeehaveTreeNode.SUCCESS + assert_that(tree.tick()).is_equal(BeehaveTreeNode.SUCCESS) + action.status = BeehaveTreeNode.RUNNING + assert_that(tree.tick()).is_equal(BeehaveTreeNode.FAILURE) + #var runner:GdUnitSceneRunner = scene_runner(tree) + #await runner.simulate_frames(1, 2000) + #assert_that(tree.tick()).is_equal(BeehaveTreeNode.RUNNING) diff --git a/test/nodes/decorators/delayer_test.gd b/test/nodes/decorators/delayer_test.gd index e7bcaede..55b09301 100644 --- a/test/nodes/decorators/delayer_test.gd +++ b/test/nodes/decorators/delayer_test.gd @@ -5,23 +5,20 @@ extends GdUnitTestSuite @warning_ignore('return_value_discarded') # TestSuite generated from -const __source = 'res://addons/beehave/nodes/decorators/delayer.gd' const __action = "res://test/actions/count_up_action.gd" -const __tree = "res://addons/beehave/nodes/beehave_tree.gd" -const __blackboard = "res://addons/beehave/blackboard.gd" var tree: BeehaveTree -var action: ActionLeaf -var delayer: DelayDecorator +var action: BeehaveAction +var delayer: BeehaveDelayer var runner:GdUnitSceneRunner func before_test() -> void: - tree = auto_free(load(__tree).new()) + tree = auto_free(BeehaveTree.new()) action = auto_free(load(__action).new()) - delayer = auto_free(load(__source).new()) + delayer = auto_free(BeehaveDelayer.new()) var actor = auto_free(Node2D.new()) - var blackboard = auto_free(load(__blackboard).new()) + var blackboard = auto_free(BeehaveBlackboard.new()) tree.add_child(delayer) delayer.add_child(action) @@ -32,25 +29,25 @@ func before_test() -> void: func test_return_success_after_delay() -> void: delayer.wait_time = get_physics_process_delta_time() - action.status = BeehaveNode.SUCCESS - assert_that(tree.tick()).is_equal(BeehaveNode.RUNNING) - assert_that(tree.tick()).is_equal(BeehaveNode.SUCCESS) + action.status = BeehaveTreeNode.SUCCESS + assert_that(tree.tick()).is_equal(BeehaveTreeNode.RUNNING) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.SUCCESS) # Assure that the delayer properly resets - assert_that(tree.tick()).is_equal(BeehaveNode.RUNNING) - assert_that(tree.tick()).is_equal(BeehaveNode.SUCCESS) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.RUNNING) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.SUCCESS) func test_return_running_after_delay() -> void: delayer.wait_time = 1.0 - action.status = BeehaveNode.RUNNING - assert_that(tree.tick()).is_equal(BeehaveNode.RUNNING) + action.status = BeehaveTreeNode.RUNNING + assert_that(tree.tick()).is_equal(BeehaveTreeNode.RUNNING) await runner.simulate_frames(1, 1000) - assert_that(tree.tick()).is_equal(BeehaveNode.RUNNING) - action.status = BeehaveNode.SUCCESS - assert_that(tree.tick()).is_equal(BeehaveNode.SUCCESS) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.RUNNING) + action.status = BeehaveTreeNode.SUCCESS + assert_that(tree.tick()).is_equal(BeehaveTreeNode.SUCCESS) # Assure that the delayer properly resets - action.status = BeehaveNode.RUNNING - assert_that(tree.tick()).is_equal(BeehaveNode.RUNNING) + action.status = BeehaveTreeNode.RUNNING + assert_that(tree.tick()).is_equal(BeehaveTreeNode.RUNNING) await runner.simulate_frames(1, 1000) - assert_that(tree.tick()).is_equal(BeehaveNode.RUNNING) - action.status = BeehaveNode.SUCCESS - assert_that(tree.tick()).is_equal(BeehaveNode.SUCCESS) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.RUNNING) + action.status = BeehaveTreeNode.SUCCESS + assert_that(tree.tick()).is_equal(BeehaveTreeNode.SUCCESS) diff --git a/test/nodes/decorators/failer_test.gd b/test/nodes/decorators/failer_test.gd index 23c9063f..fecfa151 100644 --- a/test/nodes/decorators/failer_test.gd +++ b/test/nodes/decorators/failer_test.gd @@ -5,23 +5,20 @@ extends GdUnitTestSuite @warning_ignore("return_value_discarded") # TestSuite generated from -const __source = "res://addons/beehave/nodes/decorators/failer.gd" const __action = "res://test/actions/count_up_action.gd" -const __tree = "res://addons/beehave/nodes/beehave_tree.gd" -const __blackboard = "res://addons/beehave/blackboard.gd" var tree: BeehaveTree -var action: ActionLeaf -var failer: AlwaysFailDecorator +var action: BeehaveAction +var failer: BeehaveFailer func before_test() -> void: - tree = auto_free(load(__tree).new()) + tree = auto_free(BeehaveTree.new()) action = auto_free(load(__action).new()) - failer = auto_free(load(__source).new()) + failer = auto_free(BeehaveFailer.new()) var actor = auto_free(Node2D.new()) - var blackboard = auto_free(load(__blackboard).new()) + var blackboard = auto_free(BeehaveBlackboard.new()) tree.add_child(failer) failer.add_child(action) @@ -31,14 +28,14 @@ func before_test() -> void: func test_tick() -> void: - action.status = BeehaveNode.SUCCESS - assert_that(tree.tick()).is_equal(BeehaveNode.FAILURE) + action.status = BeehaveTreeNode.SUCCESS + assert_that(tree.tick()).is_equal(BeehaveTreeNode.FAILURE) -func test_clear_running_child_after_run() -> void: - action.status = BeehaveNode.RUNNING - tree.tick() - assert_that(failer.running_child).is_equal(action) - action.status = BeehaveNode.SUCCESS - tree.tick() - assert_that(failer.running_child).is_equal(null) +#func test_clear_running_child_after_run() -> void: +# action.status = BeehaveTreeNode.RUNNING +# tree.tick() +# assert_that(failer.running_child).is_equal(action) +# action.status = BeehaveTreeNode.SUCCESS +# tree.tick() +# assert_that(failer.running_child).is_equal(null) diff --git a/test/nodes/decorators/inverter_test.gd b/test/nodes/decorators/inverter_test.gd index 4589cf98..b077b829 100644 --- a/test/nodes/decorators/inverter_test.gd +++ b/test/nodes/decorators/inverter_test.gd @@ -6,23 +6,20 @@ extends GdUnitTestSuite # TestSuite generated from -const __source = "res://addons/beehave/nodes/decorators/inverter.gd" const __action = "res://test/actions/count_up_action.gd" -const __tree = "res://addons/beehave/nodes/beehave_tree.gd" -const __blackboard = "res://addons/beehave/blackboard.gd" var tree: BeehaveTree -var action: ActionLeaf -var inverter: InverterDecorator +var action: BeehaveAction +var inverter: BeehaveInverter func before_test() -> void: - tree = auto_free(load(__tree).new()) + tree = auto_free(BeehaveTree.new()) action = auto_free(load(__action).new()) - inverter = auto_free(load(__source).new()) + inverter = auto_free(BeehaveInverter.new()) var actor = auto_free(Node2D.new()) - var blackboard = auto_free(load(__blackboard).new()) + var blackboard = auto_free(BeehaveBlackboard.new()) tree.add_child(inverter) inverter.add_child(action) @@ -32,19 +29,19 @@ func before_test() -> void: func test_invert_success_to_failure() -> void: - action.status = BeehaveNode.SUCCESS - assert_that(tree.tick()).is_equal(BeehaveNode.FAILURE) + action.status = BeehaveTreeNode.SUCCESS + assert_that(tree.tick()).is_equal(BeehaveTreeNode.FAILURE) func test_invert_failure_to_success() -> void: - action.status = BeehaveNode.FAILURE - assert_that(tree.tick()).is_equal(BeehaveNode.SUCCESS) + action.status = BeehaveTreeNode.FAILURE + assert_that(tree.tick()).is_equal(BeehaveTreeNode.SUCCESS) -func test_clear_running_child_after_run() -> void: - action.status = BeehaveNode.RUNNING - tree.tick() - assert_that(inverter.running_child).is_equal(action) - action.status = BeehaveNode.SUCCESS - tree.tick() - assert_that(inverter.running_child).is_equal(null) +#func test_clear_running_child_after_run() -> void: +# action.status = BeehaveTreeNode.RUNNING +# tree.tick() +# assert_that(inverter.running_child).is_equal(action) +# action.status = BeehaveTreeNode.SUCCESS +# tree.tick() +# assert_that(inverter.running_child).is_equal(null) diff --git a/test/nodes/decorators/limiter_test.gd b/test/nodes/decorators/limiter_test.gd index c5880708..447cdee3 100644 --- a/test/nodes/decorators/limiter_test.gd +++ b/test/nodes/decorators/limiter_test.gd @@ -4,24 +4,20 @@ extends GdUnitTestSuite @warning_ignore("unused_parameter") @warning_ignore("return_value_discarded") -# TestSuite generated from -const __source = "res://addons/beehave/nodes/decorators/limiter.gd" const __action = "res://test/actions/count_up_action.gd" -const __tree = "res://addons/beehave/nodes/beehave_tree.gd" -const __blackboard = "res://addons/beehave/blackboard.gd" var tree: BeehaveTree -var action: ActionLeaf -var limiter: LimiterDecorator +var action: BeehaveAction +var limiter: BeehaveLimiter func before_test() -> void: - tree = auto_free(load(__tree).new()) + tree = auto_free(BeehaveTree.new()) action = auto_free(load(__action).new()) - limiter = auto_free(load(__source).new()) + limiter = auto_free(BeehaveLimiter.new()) var actor = auto_free(Node2D.new()) - var blackboard = auto_free(load(__blackboard).new()) + var blackboard = auto_free(BeehaveBlackboard.new()) tree.add_child(limiter) limiter.add_child(action) @@ -32,33 +28,33 @@ func before_test() -> void: func test_max_count(count: int, _test_parameters: Array = [[2], [0]]) -> void: limiter.max_count = count - action.status = BeehaveNode.RUNNING + action.status = BeehaveTreeNode.RUNNING for i in range(count): - assert_that(tree.tick()).is_equal(BeehaveNode.RUNNING) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.RUNNING) assert_that(action.count).is_equal(count) - assert_that(tree.tick()).is_equal(BeehaveNode.FAILURE) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.FAILURE) # ensure it resets its child after it reached max count assert_that(action.count).is_equal(0) -func test_interrupt_after_run() -> void: - action.status = BeehaveNode.RUNNING - limiter.max_count = 1 - tree.tick() - assert_that(limiter.running_child).is_equal(action) - action.status = BeehaveNode.FAILURE - tree.tick() - assert_that(action.count).is_equal(0) - assert_that(limiter.running_child).is_equal(null) - - -func test_clear_running_child_after_run() -> void: - action.status = BeehaveNode.RUNNING - limiter.max_count = 10 - tree.tick() - assert_that(limiter.running_child).is_equal(action) - action.status = BeehaveNode.SUCCESS - tree.tick() - assert_that(action.count).is_equal(2) - assert_that(limiter.running_child).is_equal(null) +#func test_interrupt_after_run() -> void: +# action.status = BeehaveTreeNode.RUNNING +# limiter.max_count = 1 +# tree.tick() +# assert_that(limiter.running_child).is_equal(action) +# action.status = BeehaveTreeNode.FAILURE +# tree.tick() +# assert_that(action.count).is_equal(0) +# assert_that(limiter.running_child).is_equal(null) + + +#func test_clear_running_child_after_run() -> void: +# action.status = BeehaveTreeNode.RUNNING +# limiter.max_count = 10 +# tree.tick() +# assert_that(limiter.running_child).is_equal(action) +# action.status = BeehaveTreeNode.SUCCESS +# tree.tick() +# assert_that(action.count).is_equal(2) +# assert_that(limiter.running_child).is_equal(null) diff --git a/test/nodes/decorators/repeater_test.gd b/test/nodes/decorators/repeater_test.gd index 037a5787..ff399da7 100644 --- a/test/nodes/decorators/repeater_test.gd +++ b/test/nodes/decorators/repeater_test.gd @@ -5,21 +5,17 @@ extends GdUnitTestSuite @warning_ignore("return_value_discarded") -# TestSuite generated from -const __source = "res://addons/beehave/nodes/decorators/repeater.gd" const __action = "res://test/actions/mock_action.gd" -const __tree = "res://addons/beehave/nodes/beehave_tree.gd" -const __blackboard = "res://addons/beehave/blackboard.gd" var tree: BeehaveTree var action: MockAction -var repeater: RepeaterDecorator +var repeater: BeehaveRepeater func before_test() -> void: - tree = auto_free(load(__tree).new()) + tree = auto_free(BeehaveTree.new()) action = auto_free(load(__action).new()) - repeater = auto_free(load(__source).new()) + repeater = auto_free(BeehaveRepeater.new()) # action setup action.running_frame_count = 3 # runs for 3 frames @@ -27,7 +23,7 @@ func before_test() -> void: action.stopped_running.connect(_on_action_ended) var actor = auto_free(Node2D.new()) - var blackboard = auto_free(load(__blackboard).new()) + var blackboard = auto_free(BeehaveBlackboard.new()) tree.add_child(repeater) repeater.add_child(action) @@ -44,15 +40,15 @@ func after_test(): func test_repetitions(count: int, _test_parameters: Array = [[2], [0]]) -> void: repeater.repetitions = count - action.final_result = BeehaveNode.SUCCESS + action.final_result = BeehaveTreeNode.SUCCESS var frames_to_run = count * (action.running_frame_count + 1) # It should return `RUNNING` every frame but the last one. for i in range(frames_to_run - 1): - assert_that(tree.tick()).is_equal(BeehaveNode.RUNNING) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.RUNNING) - assert_that(tree.tick()).is_equal(BeehaveNode.SUCCESS) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.SUCCESS) var times_started = tree.blackboard.get_value("started", 0) var times_ended = tree.blackboard.get_value("ended", 0) @@ -63,10 +59,10 @@ func test_repetitions(count: int, _test_parameters: Array = [[2], [0]]) -> void: func test_failure(): repeater.repetitions = 2 - action.final_result = BeehaveNode.SUCCESS + action.final_result = BeehaveTreeNode.SUCCESS for i in range(action.running_frame_count + 1): - assert_that(tree.tick()).is_equal(BeehaveNode.RUNNING) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.RUNNING) # it should have started and ended normally var times_started = tree.blackboard.get_value("started", 0) @@ -75,11 +71,11 @@ func test_failure(): assert_int(times_started).is_equal(1) assert_int(times_ended).is_equal(1) - action.final_result = BeehaveNode.FAILURE + action.final_result = BeehaveTreeNode.FAILURE for i in range(action.running_frame_count): - assert_that(tree.tick()).is_equal(BeehaveNode.RUNNING) - assert_that(tree.tick()).is_equal(BeehaveNode.FAILURE) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.RUNNING) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.FAILURE) times_started = tree.blackboard.get_value("started", 0) times_ended = tree.blackboard.get_value("ended", 0) diff --git a/test/nodes/decorators/succeeder_test.gd b/test/nodes/decorators/succeeder_test.gd index ea8ef7ea..7f853050 100644 --- a/test/nodes/decorators/succeeder_test.gd +++ b/test/nodes/decorators/succeeder_test.gd @@ -4,24 +4,22 @@ extends GdUnitTestSuite @warning_ignore("unused_parameter") @warning_ignore("return_value_discarded") -# TestSuite generated from -const __source = "res://addons/beehave/nodes/decorators/succeeder.gd" + const __action = "res://test/actions/count_up_action.gd" -const __tree = "res://addons/beehave/nodes/beehave_tree.gd" -const __blackboard = "res://addons/beehave/blackboard.gd" + var tree: BeehaveTree -var action: ActionLeaf -var succeeder: AlwaysSucceedDecorator +var action: BeehaveAction +var succeeder: BeehaveSucceeder func before_test() -> void: - tree = auto_free(load(__tree).new()) + tree = auto_free(BeehaveTree.new()) action = auto_free(load(__action).new()) - succeeder = auto_free(load(__source).new()) + succeeder = auto_free(BeehaveSucceeder.new()) var actor = auto_free(Node2D.new()) - var blackboard = auto_free(load(__blackboard).new()) + var blackboard = auto_free(BeehaveBlackboard.new()) tree.add_child(succeeder) succeeder.add_child(action) @@ -31,14 +29,14 @@ func before_test() -> void: func test_tick() -> void: - action.status = BeehaveNode.FAILURE - assert_that(tree.tick()).is_equal(BeehaveNode.SUCCESS) + action.status = BeehaveTreeNode.FAILURE + assert_that(tree.tick()).is_equal(BeehaveTreeNode.SUCCESS) -func test_clear_running_child_after_run() -> void: - action.status = BeehaveNode.RUNNING - tree.tick() - assert_that(succeeder.running_child).is_equal(action) - action.status = BeehaveNode.SUCCESS - tree.tick() - assert_that(succeeder.running_child).is_equal(null) +#func test_clear_running_child_after_run() -> void: +# action.status = BeehaveTreeNode.RUNNING +# tree.tick() +# assert_that(succeeder.running_child).is_equal(action) +# action.status = BeehaveTreeNode.SUCCESS +# tree.tick() +# assert_that(succeeder.running_child).is_equal(null) diff --git a/test/nodes/decorators/time_limiter_test.gd b/test/nodes/decorators/time_limiter_test.gd index 1a1e100d..c14e8fb4 100644 --- a/test/nodes/decorators/time_limiter_test.gd +++ b/test/nodes/decorators/time_limiter_test.gd @@ -4,29 +4,26 @@ extends GdUnitTestSuite @warning_ignore("unused_parameter") @warning_ignore("return_value_discarded") -# TestSuite generated from -const __source = "res://addons/beehave/nodes/decorators/time_limiter.gd" + const __action = "res://test/actions/count_up_action.gd" -const __tree = "res://addons/beehave/nodes/beehave_tree.gd" -const __blackboard = "res://addons/beehave/blackboard.gd" var tree: BeehaveTree -var action: ActionLeaf -var time_limiter: TimeLimiterDecorator +var action: BeehaveAction +var time_limiter: BeehaveTimeLimiter var actor: Node2D -var blackboard: Blackboard +var blackboard: BeehaveBlackboard var runner:GdUnitSceneRunner func before_test() -> void: - tree = auto_free(load(__tree).new()) + tree = auto_free(BeehaveTree.new()) actor = auto_free(Node2D.new()) - blackboard = auto_free(load(__blackboard).new()) + blackboard = auto_free(BeehaveBlackboard.new()) tree.actor = actor tree.blackboard = blackboard action = auto_free(load(__action).new()) - time_limiter = auto_free(load(__source).new()) + time_limiter = auto_free(BeehaveTimeLimiter.new()) time_limiter.add_child(action) tree.add_child(time_limiter) @@ -36,28 +33,28 @@ func before_test() -> void: func test_return_failure_when_child_exceeds_time_limiter() -> void: time_limiter.wait_time = 1.0 - action.status = BeehaveNode.RUNNING + action.status = BeehaveTreeNode.RUNNING tree.tick() - assert_that(tree.tick()).is_equal(BeehaveNode.RUNNING) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.RUNNING) await runner.simulate_frames(1, 1500) - assert_that(tree.tick()).is_equal(BeehaveNode.FAILURE) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.FAILURE) func test_reset_when_child_finishes() -> void: time_limiter.wait_time = 0.5 - action.status = BeehaveNode.RUNNING + action.status = BeehaveTreeNode.RUNNING tree.tick() - assert_that(tree.tick()).is_equal(BeehaveNode.RUNNING) + assert_that(tree.tick()).is_equal(BeehaveTreeNode.RUNNING) await runner.simulate_frames(2, 500) - action.status = BeehaveNode.SUCCESS - assert_that(tree.tick()).is_equal(BeehaveNode.SUCCESS) - - -func test_clear_running_child_after_run() -> void: - time_limiter.wait_time = 1.5 - action.status = BeehaveNode.RUNNING - tree.tick() - assert_that(time_limiter.running_child).is_equal(action) - action.status = BeehaveNode.SUCCESS - await runner.simulate_frames(1, 1600) - assert_that(time_limiter.running_child).is_null() + action.status = BeehaveTreeNode.SUCCESS + assert_that(tree.tick()).is_equal(BeehaveTreeNode.SUCCESS) + + +#func test_clear_running_child_after_run() -> void: +# time_limiter.wait_time = 1.5 +# action.status = BeehaveTreeNode.RUNNING +# tree.tick() +# assert_that(time_limiter.running_child).is_equal(action) +# action.status = BeehaveTreeNode.SUCCESS +# await runner.simulate_frames(1, 1600) +# assert_that(time_limiter.running_child).is_null() diff --git a/test/nodes/leaves/actions/blackboard_erase_test.gd b/test/nodes/leaves/actions/blackboard_erase_test.gd index ea96873a..acdefdef 100644 --- a/test/nodes/leaves/actions/blackboard_erase_test.gd +++ b/test/nodes/leaves/actions/blackboard_erase_test.gd @@ -1,5 +1,5 @@ # GdUnit generated TestSuite -class_name BlackboardEraseActionTest +class_name BeehaveBlackboardEraseActionTest extends GdUnitTestSuite @warning_ignore("unused_parameter") @warning_ignore("return_value_discarded") @@ -10,9 +10,9 @@ const __blackboard = "res://addons/beehave/blackboard.gd" const KEY: String = "test_key" -var blackboard_erase: BlackboardEraseAction +var blackboard_erase: BeehaveBlackboardEraseAction var actor: Node -var blackboard: Blackboard +var blackboard: BeehaveBlackboard var runner: GdUnitSceneRunner @@ -28,16 +28,16 @@ func test_erase_existing_key() -> void: blackboard.set_value(KEY, 0) runner = scene_runner(blackboard_erase) - assert_that(blackboard_erase.tick(actor, blackboard)).is_equal(BeehaveNode.SUCCESS) + assert_that(blackboard_erase.tick(actor, blackboard)).is_equal(BeehaveTreeNode.SUCCESS) func test_erase_non_existing_key() -> void: runner = scene_runner(blackboard_erase) - assert_that(blackboard_erase.tick(actor, blackboard)).is_equal(BeehaveNode.SUCCESS) + assert_that(blackboard_erase.tick(actor, blackboard)).is_equal(BeehaveTreeNode.SUCCESS) func test_invalid_key_expression() -> void: blackboard_erase.key = "this is invalid!!!" runner = scene_runner(blackboard_erase) - assert_that(blackboard_erase.tick(actor, blackboard)).is_equal(BeehaveNode.FAILURE) + assert_that(blackboard_erase.tick(actor, blackboard)).is_equal(BeehaveTreeNode.FAILURE) diff --git a/test/nodes/leaves/actions/blackboard_set_test.gd b/test/nodes/leaves/actions/blackboard_set_test.gd index 4689e74c..0bf5ca26 100644 --- a/test/nodes/leaves/actions/blackboard_set_test.gd +++ b/test/nodes/leaves/actions/blackboard_set_test.gd @@ -1,5 +1,5 @@ # GdUnit generated TestSuite -class_name BlackboardSetActionTest +class_name BeehaveBlackboardSetActionTest extends GdUnitTestSuite @warning_ignore('unused_parameter') @warning_ignore('return_value_discarded') @@ -12,9 +12,9 @@ const __blackboard = "res://addons/beehave/blackboard.gd" const KEY: String = "test_key" const KEY2: String = "other_key" -var blackboard_set: BlackboardSetAction +var blackboard_set: BeehaveBlackboardSetAction var actor: Node -var blackboard: Blackboard +var blackboard: BeehaveBlackboard func before_test() -> void: @@ -27,7 +27,7 @@ func before_test() -> void: func test_set_to_constant() -> void: blackboard_set.value = "0" scene_runner(blackboard_set) # run it as a scene, so that _ready gets called - assert_that(blackboard_set.tick(actor, blackboard)).is_equal(BeehaveNode.SUCCESS) + assert_that(blackboard_set.tick(actor, blackboard)).is_equal(BeehaveTreeNode.SUCCESS) assert_bool(blackboard.has_value(KEY)).is_true() assert_that(blackboard.get_value(KEY)).is_equal(0) @@ -36,21 +36,21 @@ func test_copy_key() -> void: blackboard.set_value(KEY2, 0) blackboard_set.value = "get_value('%s')" % [KEY2] scene_runner(blackboard_set) - assert_that(blackboard_set.tick(actor, blackboard)).is_equal(BeehaveNode.SUCCESS) + assert_that(blackboard_set.tick(actor, blackboard)).is_equal(BeehaveTreeNode.SUCCESS) assert_that(blackboard.get_value(KEY)).is_equal(blackboard.get_value(KEY2)) # properly copy values from one key to another func test_invalid_expression() -> void: blackboard_set.value = "this is not a valid expression" scene_runner(blackboard_set) - assert_that(blackboard_set.tick(actor, blackboard)).is_equal(BeehaveNode.FAILURE) + assert_that(blackboard_set.tick(actor, blackboard)).is_equal(BeehaveTreeNode.FAILURE) assert_bool(blackboard.has_value(KEY)).is_false() func test_set_vector3() -> void: blackboard_set.value = "Vector3(0,0,0)" scene_runner(blackboard_set) - assert_that(blackboard_set.tick(actor, blackboard)).is_equal(BeehaveNode.SUCCESS) + assert_that(blackboard_set.tick(actor, blackboard)).is_equal(BeehaveTreeNode.SUCCESS) assert_bool(blackboard.has_value(KEY)).is_true() assert_that(blackboard.get_value(KEY)).is_equal(Vector3(0,0,0)) @@ -58,10 +58,10 @@ func test_set_vector3() -> void: func test_invalid_key_expression() -> void: blackboard_set.key = "this is invalid!!!" scene_runner(blackboard_set) - assert_that(blackboard_set.tick(actor, blackboard)).is_equal(BeehaveNode.FAILURE) + assert_that(blackboard_set.tick(actor, blackboard)).is_equal(BeehaveTreeNode.FAILURE) func test_invalid_value_expression() -> void: blackboard_set.value = "this is invalid!!!" scene_runner(blackboard_set) - assert_that(blackboard_set.tick(actor, blackboard)).is_equal(BeehaveNode.FAILURE) + assert_that(blackboard_set.tick(actor, blackboard)).is_equal(BeehaveTreeNode.FAILURE) diff --git a/test/nodes/leaves/conditions/blackboard_compare_test.gd b/test/nodes/leaves/conditions/blackboard_compare_test.gd index de584637..db8c4366 100644 --- a/test/nodes/leaves/conditions/blackboard_compare_test.gd +++ b/test/nodes/leaves/conditions/blackboard_compare_test.gd @@ -1,5 +1,5 @@ # GdUnit generated TestSuite -class_name BlackboardCompareConditionTest +class_name BeehaveBlackboardCompareConditionTest extends GdUnitTestSuite @warning_ignore("unused_parameter") @warning_ignore("return_value_discarded") @@ -9,9 +9,9 @@ const __source = "res://addons/beehave/nodes/leaves/blackboard_compare.gd" const __blackboard = "res://addons/beehave/blackboard.gd" -var blackboard_compare: BlackboardCompareCondition +var blackboard_compare: BeehaveBlackboardCompareCondition var actor: Node -var blackboard: Blackboard +var blackboard: BeehaveBlackboard var runner: GdUnitSceneRunner @@ -25,28 +25,28 @@ func before_test() -> void: func test_comparison_operators() -> void: var data: Dictionary = { ["0", "0"]: [ - [BlackboardCompareCondition.Operators.EQUAL, BeehaveNode.SUCCESS], - [BlackboardCompareCondition.Operators.NOT_EQUAL, BeehaveNode.FAILURE], - [BlackboardCompareCondition.Operators.GREATER, BeehaveNode.FAILURE], - [BlackboardCompareCondition.Operators.LESS, BeehaveNode.FAILURE], - [BlackboardCompareCondition.Operators.GREATER_EQUAL, BeehaveNode.SUCCESS], - [BlackboardCompareCondition.Operators.LESS_EQUAL, BeehaveNode.SUCCESS], + [BlackboardCompareCondition.Operators.EQUAL, BeehaveTreeNode.SUCCESS], + [BlackboardCompareCondition.Operators.NOT_EQUAL, BeehaveTreeNode.FAILURE], + [BlackboardCompareCondition.Operators.GREATER, BeehaveTreeNode.FAILURE], + [BlackboardCompareCondition.Operators.LESS, BeehaveTreeNode.FAILURE], + [BlackboardCompareCondition.Operators.GREATER_EQUAL, BeehaveTreeNode.SUCCESS], + [BlackboardCompareCondition.Operators.LESS_EQUAL, BeehaveTreeNode.SUCCESS], ], ["0", "1"]: [ - [BlackboardCompareCondition.Operators.EQUAL, BeehaveNode.FAILURE], - [BlackboardCompareCondition.Operators.NOT_EQUAL, BeehaveNode.SUCCESS], - [BlackboardCompareCondition.Operators.GREATER, BeehaveNode.FAILURE], - [BlackboardCompareCondition.Operators.LESS, BeehaveNode.SUCCESS], - [BlackboardCompareCondition.Operators.GREATER_EQUAL, BeehaveNode.FAILURE], - [BlackboardCompareCondition.Operators.LESS_EQUAL, BeehaveNode.SUCCESS], + [BlackboardCompareCondition.Operators.EQUAL, BeehaveTreeNode.FAILURE], + [BlackboardCompareCondition.Operators.NOT_EQUAL, BeehaveTreeNode.SUCCESS], + [BlackboardCompareCondition.Operators.GREATER, BeehaveTreeNode.FAILURE], + [BlackboardCompareCondition.Operators.LESS, BeehaveTreeNode.SUCCESS], + [BlackboardCompareCondition.Operators.GREATER_EQUAL, BeehaveTreeNode.FAILURE], + [BlackboardCompareCondition.Operators.LESS_EQUAL, BeehaveTreeNode.SUCCESS], ], ["1", "0"]: [ - [BlackboardCompareCondition.Operators.EQUAL, BeehaveNode.FAILURE], - [BlackboardCompareCondition.Operators.NOT_EQUAL, BeehaveNode.SUCCESS], - [BlackboardCompareCondition.Operators.GREATER, BeehaveNode.SUCCESS], - [BlackboardCompareCondition.Operators.LESS, BeehaveNode.FAILURE], - [BlackboardCompareCondition.Operators.GREATER_EQUAL, BeehaveNode.SUCCESS], - [BlackboardCompareCondition.Operators.LESS_EQUAL, BeehaveNode.FAILURE], + [BlackboardCompareCondition.Operators.EQUAL, BeehaveTreeNode.FAILURE], + [BlackboardCompareCondition.Operators.NOT_EQUAL, BeehaveTreeNode.SUCCESS], + [BlackboardCompareCondition.Operators.GREATER, BeehaveTreeNode.SUCCESS], + [BlackboardCompareCondition.Operators.LESS, BeehaveTreeNode.FAILURE], + [BlackboardCompareCondition.Operators.GREATER_EQUAL, BeehaveTreeNode.SUCCESS], + [BlackboardCompareCondition.Operators.LESS_EQUAL, BeehaveTreeNode.FAILURE], ] } @@ -54,7 +54,7 @@ func test_comparison_operators() -> void: for pair in data[operands]: blackboard_compare = auto_free(load(__source).new()) - var operator: BlackboardCompareCondition.Operators = pair[0] + var operator: BeehaveBlackboardCompareCondition.Operators = pair[0] var expected_status: int = pair[1] blackboard_compare.left_operand = operands[0] @@ -70,26 +70,26 @@ func test_blackboard_access() -> void: blackboard.set_value("direction", Vector3.FORWARD) blackboard_compare.left_operand = "get_value(\"direction\").length()" - blackboard_compare.operator = BlackboardCompareCondition.Operators.EQUAL + blackboard_compare.operator = BeehaveBlackboardCompareCondition.Operators.EQUAL blackboard_compare.right_operand = "1" runner = scene_runner(blackboard_compare) - assert_that(blackboard_compare.tick(actor, blackboard)).is_equal(BeehaveNode.SUCCESS) + assert_that(blackboard_compare.tick(actor, blackboard)).is_equal(BeehaveTreeNode.SUCCESS) func test_invalid_left_operand_expression() -> void: blackboard_compare.left_operand = "this is invalid!!!" - blackboard_compare.operator = BlackboardCompareCondition.Operators.EQUAL + blackboard_compare.operator = BeehaveBlackboardCompareCondition.Operators.EQUAL blackboard_compare.right_operand = "1" runner = scene_runner(blackboard_compare) - assert_that(blackboard_compare.tick(actor, blackboard)).is_equal(BeehaveNode.FAILURE) + assert_that(blackboard_compare.tick(actor, blackboard)).is_equal(BeehaveTreeNode.FAILURE) func test_invalid_right_operand_expression() -> void: blackboard_compare.left_operand = "1" - blackboard_compare.operator = BlackboardCompareCondition.Operators.EQUAL + blackboard_compare.operator = BeehaveBlackboardCompareCondition.Operators.EQUAL blackboard_compare.right_operand = "this is invalid!!!" runner = scene_runner(blackboard_compare) - assert_that(blackboard_compare.tick(actor, blackboard)).is_equal(BeehaveNode.FAILURE) + assert_that(blackboard_compare.tick(actor, blackboard)).is_equal(BeehaveTreeNode.FAILURE) diff --git a/test/nodes/leaves/conditions/blackboard_has_test.gd b/test/nodes/leaves/conditions/blackboard_has_test.gd index fbd14a0d..54d0a386 100644 --- a/test/nodes/leaves/conditions/blackboard_has_test.gd +++ b/test/nodes/leaves/conditions/blackboard_has_test.gd @@ -1,5 +1,5 @@ # GdUnit generated TestSuite -class_name BlackboardHasConditionTest +class_name BeehaveBlackboardHasConditionTest extends GdUnitTestSuite @warning_ignore('unused_parameter') @warning_ignore('return_value_discarded') @@ -11,9 +11,9 @@ const __blackboard = "res://addons/beehave/blackboard.gd" const KEY: String = "test_key" -var blackboard_has: BlackboardHasCondition +var blackboard_has: BeehaveBlackboardHasCondition var actor: Node -var blackboard: Blackboard +var blackboard: BeehaveBlackboard var runner: GdUnitSceneRunner @@ -29,7 +29,7 @@ func test_key_exists() -> void: blackboard.set_value(KEY, 0) runner = scene_runner(blackboard_has) - assert_that(blackboard_has.tick(actor, blackboard)).is_equal(BeehaveNode.SUCCESS) + assert_that(blackboard_has.tick(actor, blackboard)).is_equal(BeehaveTreeNode.SUCCESS) func test_variant_key_exists() -> void: @@ -37,30 +37,30 @@ func test_variant_key_exists() -> void: blackboard_has.key = "Vector2(0, 0)" runner = scene_runner(blackboard_has) - assert_that(blackboard_has.tick(actor, blackboard)).is_equal(BeehaveNode.SUCCESS) + assert_that(blackboard_has.tick(actor, blackboard)).is_equal(BeehaveTreeNode.SUCCESS) func test_key_exists_but_value_null() -> void: blackboard.set_value(KEY, null) runner = scene_runner(blackboard_has) - assert_that(blackboard_has.tick(actor, blackboard)).is_equal(BeehaveNode.FAILURE) + assert_that(blackboard_has.tick(actor, blackboard)).is_equal(BeehaveTreeNode.FAILURE) func test_key_does_not_exist() -> void: runner = scene_runner(blackboard_has) - assert_that(blackboard_has.tick(actor, blackboard)).is_equal(BeehaveNode.FAILURE) + assert_that(blackboard_has.tick(actor, blackboard)).is_equal(BeehaveTreeNode.FAILURE) func test_invalid_key_expression() -> void: blackboard_has.key = "this is invalid!!!" runner = scene_runner(blackboard_has) - assert_that(blackboard_has.tick(actor, blackboard)).is_equal(BeehaveNode.FAILURE) + assert_that(blackboard_has.tick(actor, blackboard)).is_equal(BeehaveTreeNode.FAILURE) func test_invalid_key_expression_wrong_format() -> void: blackboard_has.key = KEY runner = scene_runner(blackboard_has) - assert_that(blackboard_has.tick(actor, blackboard)).is_equal(BeehaveNode.FAILURE) + assert_that(blackboard_has.tick(actor, blackboard)).is_equal(BeehaveTreeNode.FAILURE) diff --git a/test/randomized_composites/weighted_sampling/selector_random/selector_random_weights.gd b/test/randomized_composites/weighted_sampling/selector_random/selector_random_weights.gd index 9a63be0d..df5044f9 100644 --- a/test/randomized_composites/weighted_sampling/selector_random/selector_random_weights.gd +++ b/test/randomized_composites/weighted_sampling/selector_random/selector_random_weights.gd @@ -2,7 +2,7 @@ extends Node2D @onready var selector: SelectorRandomComposite = %SelectorRandom -@onready var blackboard: Blackboard = $Blackboard +@onready var blackboard: BeehaveBlackboard = $Blackboard @onready var label: Label = $Label # key: Node name | value: counter key diff --git a/test/unit_test_scene.gd b/test/unit_test_scene.gd index 38e892f8..f825952b 100644 --- a/test/unit_test_scene.gd +++ b/test/unit_test_scene.gd @@ -1,8 +1,15 @@ class_name UnitTestScene extends Node2D @onready var beehave_tree:BeehaveTree = %BeehaveTree -@onready var blackboard:Blackboard = %Blackboard -@onready var test_node = $Node2D +@onready var beehave_tree_2:BeehaveTree = %BeehaveTree +@onready var blackboard:BeehaveBlackboard = %BeehaveBlackboard +@onready var test_node = $TestNode @onready var count_up_action = %CountUpAction @onready var shared_action_1 = %SharedCountUpAction1 @onready var shared_action_2 = %SharedCountUpAction2 + + +func _ready() -> void: + blackboard.set_value("a", 2) + blackboard.set_value("b", 3) + blackboard.set_value("c", 4) diff --git a/test/unit_test_scene.tscn b/test/unit_test_scene.tscn index 010cc27f..bf08c283 100644 --- a/test/unit_test_scene.tscn +++ b/test/unit_test_scene.tscn @@ -1,105 +1,42 @@ -[gd_scene load_steps=8 format=3 uid="uid://diw3kjj050wdy"] +[gd_scene load_steps=3 format=3 uid="uid://bjyg8sehtnp6u"] -[ext_resource type="Script" path="res://addons/beehave/blackboard.gd" id="1_27ukk"] -[ext_resource type="Script" path="res://test/unit_test_scene.gd" id="1_embiv"] -[ext_resource type="Script" path="res://addons/beehave/nodes/beehave_tree.gd" id="2_phgmn"] -[ext_resource type="Script" path="res://addons/beehave/nodes/composites/sequence.gd" id="4_px6t4"] -[ext_resource type="Script" path="res://test/conditions/value_reached_condition.gd" id="5_rc0ra"] -[ext_resource type="Script" path="res://addons/beehave/nodes/decorators/inverter.gd" id="5_thhls"] -[ext_resource type="Script" path="res://test/actions/count_up_action.gd" id="6_brhwp"] +[ext_resource type="Script" path="res://test/unit_test_scene.gd" id="1_1kcjy"] +[ext_resource type="Script" path="res://test/actions/count_up_action.gd" id="2_gwebm"] [node name="UnitTestScene" type="Node2D"] -script = ExtResource("1_embiv") +script = ExtResource("1_1kcjy") -[node name="Blackboard" type="Node" parent="."] +[node name="BeehaveTree" type="BeehaveTree" parent="." node_paths=PackedStringArray("blackboard")] +process_thread = 0 +enabled = true +blackboard = NodePath("../BeehaveBlackboard") unique_name_in_owner = true -script = ExtResource("1_27ukk") -[node name="Node2D" type="Node2D" parent="."] +[node name="BeehaveSucceeder" type="BeehaveSucceeder" parent="BeehaveTree"] -[node name="BeehaveTree" type="Node" parent="Node2D" node_paths=PackedStringArray("blackboard")] +[node name="CountUpAction" type="BeehaveAction" parent="BeehaveTree/BeehaveSucceeder"] unique_name_in_owner = true -script = ExtResource("2_phgmn") -blackboard = NodePath("@Node@22048") +script = ExtResource("2_gwebm") -[node name="SequenceComposite" type="Node" parent="Node2D/BeehaveTree"] -script = ExtResource("4_px6t4") - -[node name="ValueNotReached" type="Node" parent="Node2D/BeehaveTree/SequenceComposite"] -script = ExtResource("5_thhls") - -[node name="ValueReachedCondition" type="Node" parent="Node2D/BeehaveTree/SequenceComposite/ValueNotReached"] -script = ExtResource("5_rc0ra") - -[node name="CountUpAction" type="Node" parent="Node2D/BeehaveTree/SequenceComposite"] +[node name="BeehaveTree2" type="BeehaveTree" parent="." node_paths=PackedStringArray("blackboard")] +process_thread = 0 +enabled = true +blackboard = NodePath("../BeehaveBlackboard") unique_name_in_owner = true -script = ExtResource("6_brhwp") - -[node name="AnotherNode" type="Node2D" parent="."] - -[node name="BeehaveTree" type="Node" parent="AnotherNode" node_paths=PackedStringArray("blackboard")] -script = ExtResource("2_phgmn") -actor_node_path = NodePath("..") -blackboard = NodePath("../../Blackboard") -[node name="SequenceComposite" type="Node" parent="AnotherNode/BeehaveTree"] -script = ExtResource("4_px6t4") +[node name="BeehaveSucceeder" type="BeehaveSucceeder" parent="BeehaveTree2"] -[node name="ValueNotReached" type="Node" parent="AnotherNode/BeehaveTree/SequenceComposite"] -script = ExtResource("5_thhls") - -[node name="ValueReachedCondition" type="Node" parent="AnotherNode/BeehaveTree/SequenceComposite/ValueNotReached"] -script = ExtResource("5_rc0ra") -limit = 4 - -[node name="SharedCountUpAction1" type="Node" parent="AnotherNode/BeehaveTree/SequenceComposite"] +[node name="SharedCountUpAction1" type="BeehaveAction" parent="BeehaveTree2/BeehaveSucceeder"] unique_name_in_owner = true -script = ExtResource("6_brhwp") - -[node name="ThirdNode" type="Node2D" parent="."] - -[node name="BeehaveTree" type="Node" parent="ThirdNode" node_paths=PackedStringArray("blackboard")] -script = ExtResource("2_phgmn") -actor_node_path = NodePath("..") -blackboard = NodePath("../../Blackboard") +script = ExtResource("2_gwebm") -[node name="SequenceComposite" type="Node" parent="ThirdNode/BeehaveTree"] -script = ExtResource("4_px6t4") +[node name="BeehaveSucceeder2" type="BeehaveSucceeder" parent="BeehaveTree2"] -[node name="ValueNotReached" type="Node" parent="ThirdNode/BeehaveTree/SequenceComposite"] -script = ExtResource("5_thhls") - -[node name="ValueReachedCondition" type="Node" parent="ThirdNode/BeehaveTree/SequenceComposite/ValueNotReached"] -script = ExtResource("5_rc0ra") -limit = 4 - -[node name="SharedCountUpAction2" type="Node" parent="ThirdNode/BeehaveTree/SequenceComposite"] +[node name="SharedCountUpAction2" type="BeehaveAction" parent="BeehaveTree2/BeehaveSucceeder2"] unique_name_in_owner = true -script = ExtResource("6_brhwp") - -[node name="EmptyTree" type="Node2D" parent="."] - -[node name="BeehaveTree" type="Node" parent="EmptyTree" node_paths=PackedStringArray("blackboard")] -script = ExtResource("2_phgmn") -blackboard = NodePath("@Node@22045") - -[node name="OnlyOneActionTree" type="Node2D" parent="."] +script = ExtResource("2_gwebm") -[node name="BeehaveTree" type="Node" parent="OnlyOneActionTree" node_paths=PackedStringArray("blackboard")] -script = ExtResource("2_phgmn") -enabled = false -actor_node_path = NodePath("..") -blackboard = NodePath("@Node@22046") - -[node name="CountUpAction" type="Node" parent="OnlyOneActionTree/BeehaveTree"] -script = ExtResource("6_brhwp") - -[node name="DeactivatedTree" type="Node2D" parent="."] - -[node name="BeehaveTree" type="Node" parent="DeactivatedTree" node_paths=PackedStringArray("blackboard")] -script = ExtResource("2_phgmn") -actor_node_path = NodePath("..") -blackboard = NodePath("@Node@22047") +[node name="BeehaveBlackboard" type="BeehaveBlackboard" parent="."] +unique_name_in_owner = true -[node name="CountUpAction" type="Node" parent="DeactivatedTree/BeehaveTree"] -script = ExtResource("6_brhwp") +[node name="TestNode" type="Node2D" parent="."]