From 613fb4c08aad2d22c9e5d73c61798e79474b2e3a Mon Sep 17 00:00:00 2001 From: Ben10Omintrix <138636438+Ben10Omintrix@users.noreply.github.com> Date: Sat, 21 Sep 2024 01:29:34 +0300 Subject: [PATCH] ai controllers that fail to make a plan no longer process until theyre able to plan again (#86600) ## About The Pull Request ai controllers that fail planning no longer process until theyre able to plan again. this makes it so /process is called less thus you'll have less AI competing against one another for cpu. also converts idle behaviors into singletons ## Why It's Good For The Game AIs that dont have a plan dont do anything during processing so its better to just make them sit out the cycle instead of draining cpu ## Changelog :cl: /:cl: --- code/controllers/subsystem/ai_controllers.dm | 6 ++--- .../subsystem/processing/ai_idle_behaviors.dm | 15 +++++++++-- code/datums/ai/_ai_controller.dm | 26 ++++++++++++------- 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/code/controllers/subsystem/ai_controllers.dm b/code/controllers/subsystem/ai_controllers.dm index e7aa6b53fdc..49c571a9a07 100644 --- a/code/controllers/subsystem/ai_controllers.dm +++ b/code/controllers/subsystem/ai_controllers.dm @@ -23,14 +23,12 @@ SUBSYSTEM_DEF(ai_controllers) /datum/controller/subsystem/ai_controllers/fire(resumed) var/timer = TICK_USAGE_REAL for(var/datum/ai_controller/ai_controller as anything in GLOB.ai_controllers_by_status[planning_status]) - if(!COOLDOWN_FINISHED(ai_controller, failed_planning_cooldown)) - continue - if(!ai_controller.able_to_plan) continue ai_controller.SelectBehaviors(wait * 0.1) + if(!length(ai_controller.current_behaviors)) //Still no plan - COOLDOWN_START(ai_controller, failed_planning_cooldown, AI_FAILED_PLANNING_COOLDOWN) + ai_controller.planning_failed() our_cost = MC_AVERAGE(our_cost, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer)) diff --git a/code/controllers/subsystem/processing/ai_idle_behaviors.dm b/code/controllers/subsystem/processing/ai_idle_behaviors.dm index cda3d354882..8875d971ad8 100644 --- a/code/controllers/subsystem/processing/ai_idle_behaviors.dm +++ b/code/controllers/subsystem/processing/ai_idle_behaviors.dm @@ -1,6 +1,17 @@ PROCESSING_SUBSYSTEM_DEF(idle_ai_behaviors) - name = "idle_ai_behaviors" - flags = SS_NO_INIT | SS_BACKGROUND + name = "AI Idle Behaviors" + flags = SS_BACKGROUND wait = 1.5 SECONDS priority = FIRE_PRIORITY_IDLE_NPC init_order = INIT_ORDER_AI_IDLE_CONTROLLERS //must execute only after ai behaviors are initialized + ///List of all the idle ai behaviors + var/list/idle_behaviors = list() + +/datum/controller/subsystem/processing/idle_ai_behaviors/Initialize() + setup_idle_behaviors() + return SS_INIT_SUCCESS + +/datum/controller/subsystem/processing/idle_ai_behaviors/proc/setup_idle_behaviors() + for(var/behavior_type in subtypesof(/datum/idle_behavior)) + var/datum/idle_behavior/behavior = new behavior_type + idle_behaviors[behavior_type] = behavior diff --git a/code/datums/ai/_ai_controller.dm b/code/datums/ai/_ai_controller.dm index 499da54b1b1..4d5b6600443 100644 --- a/code/datums/ai/_ai_controller.dm +++ b/code/datums/ai/_ai_controller.dm @@ -39,8 +39,6 @@ multiple modular subtrees with behaviors var/continue_processing_when_client = FALSE ///distance to give up on target var/max_target_distance = 14 - ///Cooldown for new plans, to prevent AI from going nuts if it can't think of new plans and looping on end - COOLDOWN_DECLARE(failed_planning_cooldown) ///All subtrees this AI has available, will run them in order, so make sure they're in the order you want them to run. On initialization of this type, it will start as a typepath(s) and get converted to references of ai_subtrees found in SSai_controllers when init_subtrees() is called var/list/planning_subtrees @@ -69,14 +67,15 @@ multiple modular subtrees with behaviors var/able_to_run = FALSE /// are we even able to plan? var/able_to_plan = TRUE + /// are we currently on failed planning timeout? + var/on_failed_planning_timeout = FALSE /datum/ai_controller/New(atom/new_pawn) change_ai_movement_type(ai_movement) init_subtrees() - if(idle_behavior) - idle_behavior = new idle_behavior() + idle_behavior = SSidle_ai_behaviors.idle_behaviors[idle_behavior] if(!isnull(new_pawn)) // unit tests need the ai_controller to exist in isolation due to list schenanigans i hate it here PossessPawn(new_pawn) @@ -240,7 +239,7 @@ multiple modular subtrees with behaviors if(!pawn_turf) CRASH("AI controller [src] controlling pawn ([pawn]) is not on a turf.") #endif - if(!length(SSmobs.clients_by_zlevel[pawn_turf.z])) + if(!length(SSmobs.clients_by_zlevel[pawn_turf.z]) || on_failed_planning_timeout || !able_to_run) return AI_STATUS_OFF if(should_idle()) return AI_STATUS_IDLE @@ -300,6 +299,9 @@ multiple modular subtrees with behaviors /datum/ai_controller/proc/update_able_to_run() SIGNAL_HANDLER able_to_run = get_able_to_run() + if(!able_to_run) + GLOB.move_manager.stop_looping(pawn) //stop moving + set_ai_status(get_expected_ai_status()) ///Returns TRUE if the ai controller can actually run at the moment, FALSE otherwise /datum/ai_controller/proc/get_able_to_run() @@ -312,10 +314,6 @@ multiple modular subtrees with behaviors ///Runs any actions that are currently running /datum/ai_controller/process(seconds_per_tick) - if(!able_to_run) - GLOB.move_manager.stop_looping(pawn) //stop moving - return //this should remove them from processing in the future through event-based stuff. - if(!length(current_behaviors) && idle_behavior) idle_behavior.perform_idle_behavior(seconds_per_tick, src) //Do some stupid shit while we have nothing to do return @@ -491,6 +489,7 @@ multiple modular subtrees with behaviors /datum/ai_controller/proc/on_stat_changed(mob/living/source, new_stat) SIGNAL_HANDLER reset_ai_status() + update_able_to_run() /datum/ai_controller/proc/on_sentience_gained() SIGNAL_HANDLER @@ -531,6 +530,15 @@ multiple modular subtrees with behaviors minimum_distance = iter_behavior.required_distance return minimum_distance +/datum/ai_controller/proc/planning_failed() + on_failed_planning_timeout = TRUE + set_ai_status(get_expected_ai_status()) + addtimer(CALLBACK(src, PROC_REF(resume_planning)), AI_FAILED_PLANNING_COOLDOWN) + +/datum/ai_controller/proc/resume_planning() + on_failed_planning_timeout = FALSE + set_ai_status(get_expected_ai_status()) + /// Returns true if we have a blackboard key with the provided key and it is not qdeleting /datum/ai_controller/proc/blackboard_key_exists(key) var/datum/key_value = blackboard[key]