From 11187a2a6d9a4fe5dc2bd0750ac4d1d9bd5cc0b6 Mon Sep 17 00:00:00 2001 From: Ben10Omintrix <138636438+Ben10Omintrix@users.noreply.github.com> Date: Sun, 6 Oct 2024 07:50:02 +0300 Subject: [PATCH] some more ai optimizations (#86975) ## About The Pull Request ai controllers that have exhausted all their current behaviors now stop processing until the next cycle, so we no longer need to do these checks on every process fire. idle behaviors are now instead handled by a new low priority subsystem. these are the costs before/after roughly 25 minutes into the round ![image](https://github.com/user-attachments/assets/28b93cf8-b929-432a-a17d-61cbd581cddf) ## Why It's Good For The Game improves ai performance ## Changelog :cl: /:cl: --- code/__DEFINES/MC.dm | 8 ++++ code/__DEFINES/subsystems.dm | 1 + code/_globalvars/lists/basic_ai.dm | 7 +++ .../unplanned_ai_idle_controllers.dm | 4 ++ .../subsystem/unplanned_controllers.dm | 18 ++++++++ code/datums/ai/_ai_controller.dm | 43 +++++++++++++++---- .../ai/idle_behaviors/_idle_behavior.dm | 3 +- code/datums/ai/idle_behaviors/idle_dog.dm | 1 + code/datums/ai/idle_behaviors/idle_haunted.dm | 1 + code/datums/ai/idle_behaviors/idle_monkey.dm | 1 + tgstation.dme | 2 + 11 files changed, 80 insertions(+), 9 deletions(-) create mode 100644 code/controllers/subsystem/unplanned_ai_idle_controllers.dm create mode 100644 code/controllers/subsystem/unplanned_controllers.dm diff --git a/code/__DEFINES/MC.dm b/code/__DEFINES/MC.dm index 2de9e7140ad..115748a9a82 100644 --- a/code/__DEFINES/MC.dm +++ b/code/__DEFINES/MC.dm @@ -141,3 +141,11 @@ }\ /datum/controller/subsystem/ai_controllers/##X/fire() {..() /*just so it shows up on the profiler*/} \ /datum/controller/subsystem/ai_controllers/##X + +#define UNPLANNED_CONTROLLER_SUBSYSTEM_DEF(X) GLOBAL_REAL(SS##X, /datum/controller/subsystem/unplanned_controllers/##X);\ +/datum/controller/subsystem/unplanned_controllers/##X/New(){\ + NEW_SS_GLOBAL(SS##X);\ + PreInit();\ +}\ +/datum/controller/subsystem/unplanned_controllers/##X/fire() {..() /*just so it shows up on the profiler*/} \ +/datum/controller/subsystem/unplanned_controllers/##X diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index c5e3172533a..ea707e33a1f 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -193,6 +193,7 @@ // Subsystem fire priority, from lowest to highest priority // If the subsystem isn't listed here it's either DEFAULT or PROCESS (if it's a processing subsystem child) +#define FIRE_PRIORITY_UNPLANNED_NPC 3 #define FIRE_PRIORITY_IDLE_NPC 5 #define FIRE_PRIORITY_PING 10 #define FIRE_PRIORITY_SERVER_MAINT 10 diff --git a/code/_globalvars/lists/basic_ai.dm b/code/_globalvars/lists/basic_ai.dm index 8d79c9bfafe..a8646bb8d7f 100644 --- a/code/_globalvars/lists/basic_ai.dm +++ b/code/_globalvars/lists/basic_ai.dm @@ -10,3 +10,10 @@ GLOBAL_LIST_INIT(ai_controllers_by_status, list( ///basic ai controllers based on their z level GLOBAL_LIST_EMPTY(ai_controllers_by_zlevel) + +///basic ai controllers that are currently performing idled behaviors +GLOBAL_LIST_INIT(unplanned_controllers, list( + AI_STATUS_ON = list(), + AI_STATUS_IDLE = list(), +)) + diff --git a/code/controllers/subsystem/unplanned_ai_idle_controllers.dm b/code/controllers/subsystem/unplanned_ai_idle_controllers.dm new file mode 100644 index 00000000000..6385239e18c --- /dev/null +++ b/code/controllers/subsystem/unplanned_ai_idle_controllers.dm @@ -0,0 +1,4 @@ +UNPLANNED_CONTROLLER_SUBSYSTEM_DEF(idle_unplanned_controllers) + name = "Unplanned AI Idle Controllers" + wait = 2.5 SECONDS + target_status = AI_STATUS_IDLE diff --git a/code/controllers/subsystem/unplanned_controllers.dm b/code/controllers/subsystem/unplanned_controllers.dm new file mode 100644 index 00000000000..3fb5f46dd06 --- /dev/null +++ b/code/controllers/subsystem/unplanned_controllers.dm @@ -0,0 +1,18 @@ +/// Handles making mobs perform lightweight "idle" behaviors such as wandering around when they have nothing planned +SUBSYSTEM_DEF(unplanned_controllers) + name = "Unplanned AI Controllers" + flags = SS_POST_FIRE_TIMING|SS_BACKGROUND|SS_NO_INIT + priority = FIRE_PRIORITY_UNPLANNED_NPC + init_order = INIT_ORDER_AI_CONTROLLERS + wait = 0.25 SECONDS + runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME + ///what ai status are we interested in + var/target_status = AI_STATUS_ON + +/datum/controller/subsystem/unplanned_controllers/stat_entry(msg) + msg = "Planning AIs:[length(GLOB.unplanned_controllers[target_status])]" + return ..() + +/datum/controller/subsystem/unplanned_controllers/fire(resumed) + for(var/datum/ai_controller/ai_controller as anything in GLOB.unplanned_controllers[target_status]) + ai_controller.idle_behavior.perform_idle_behavior(wait * 0.1, ai_controller) diff --git a/code/datums/ai/_ai_controller.dm b/code/datums/ai/_ai_controller.dm index 239f02b05b8..76e87bc14f3 100644 --- a/code/datums/ai/_ai_controller.dm +++ b/code/datums/ai/_ai_controller.dm @@ -284,6 +284,7 @@ multiple modular subtrees with behaviors GLOB.ai_controllers_by_zlevel[pawn_turf.z] -= src if(ai_status) GLOB.ai_controllers_by_status[ai_status] -= src + remove_from_unplanned_controllers() pawn.ai_controller = null pawn = null if(destroy) @@ -340,10 +341,6 @@ multiple modular subtrees with behaviors ///Runs any actions that are currently running /datum/ai_controller/process(seconds_per_tick) - 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 - if(current_movement_target) if(!isatom(current_movement_target)) stack_trace("[pawn]'s current movement target is not an atom, rather a [current_movement_target.type]! Did you accidentally set it to a weakref?") @@ -354,7 +351,6 @@ multiple modular subtrees with behaviors CancelActions() return - for(var/datum/ai_behavior/current_behavior as anything in current_behaviors) // Convert the current behaviour action cooldown to realtime seconds from deciseconds.current_behavior @@ -417,17 +413,24 @@ multiple modular subtrees with behaviors //remove old status, if we've got one if(ai_status) GLOB.ai_controllers_by_status[ai_status] -= src + remove_from_unplanned_controllers() stop_previous_processing() ai_status = new_ai_status GLOB.ai_controllers_by_status[new_ai_status] += src + if(ai_status == AI_STATUS_OFF) + CancelActions() + return + if(!length(current_behaviors)) + add_to_unplanned_controllers() + return + start_ai_processing() + +/datum/ai_controller/proc/start_ai_processing() switch(ai_status) if(AI_STATUS_ON) START_PROCESSING(SSai_behaviors, src) if(AI_STATUS_IDLE) START_PROCESSING(SSidle_ai_behaviors, src) - CancelActions() - if(AI_STATUS_OFF) - CancelActions() /datum/ai_controller/proc/stop_previous_processing() switch(ai_status) @@ -441,6 +444,16 @@ multiple modular subtrees with behaviors update_able_to_run() addtimer(CALLBACK(src, PROC_REF(update_able_to_run)), time) +/datum/ai_controller/proc/add_to_unplanned_controllers() + if(isnull(ai_status) || ai_status == AI_STATUS_OFF || isnull(idle_behavior)) + return + GLOB.unplanned_controllers[ai_status][src] = TRUE + +/datum/ai_controller/proc/remove_from_unplanned_controllers() + if(isnull(ai_status) || ai_status == AI_STATUS_OFF) + return + GLOB.unplanned_controllers[ai_status] -= src + /datum/ai_controller/proc/modify_cooldown(datum/ai_behavior/behavior, new_cooldown) behavior_cooldowns[behavior] = new_cooldown @@ -459,6 +472,7 @@ multiple modular subtrees with behaviors if(!behavior.setup(arglist(arguments))) return + var/should_exit_unplanned = !length(current_behaviors) planned_behaviors[behavior] = TRUE current_behaviors[behavior] = TRUE @@ -471,6 +485,9 @@ multiple modular subtrees with behaviors if(!(behavior.behavior_flags & AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION)) //this one blocks planning! able_to_plan = FALSE + if(should_exit_unplanned) + exit_unplanned_mode() + SEND_SIGNAL(src, AI_CONTROLLER_BEHAVIOR_QUEUED(behavior_type), arguments) /datum/ai_controller/proc/check_able_to_plan() @@ -482,6 +499,16 @@ multiple modular subtrees with behaviors /datum/ai_controller/proc/dequeue_behavior(datum/ai_behavior/behavior) current_behaviors -= behavior able_to_plan = check_able_to_plan() + if(!length(current_behaviors)) + enter_unplanned_mode() + +/datum/ai_controller/proc/exit_unplanned_mode() + remove_from_unplanned_controllers() + start_ai_processing() + +/datum/ai_controller/proc/enter_unplanned_mode() + add_to_unplanned_controllers() + stop_previous_processing() /datum/ai_controller/proc/ProcessBehavior(seconds_per_tick, datum/ai_behavior/behavior) var/list/arguments = list(seconds_per_tick, src) diff --git a/code/datums/ai/idle_behaviors/_idle_behavior.dm b/code/datums/ai/idle_behaviors/_idle_behavior.dm index 315233bb71d..bacb8e7cdf3 100644 --- a/code/datums/ai/idle_behaviors/_idle_behavior.dm +++ b/code/datums/ai/idle_behaviors/_idle_behavior.dm @@ -1,4 +1,5 @@ /datum/idle_behavior /datum/idle_behavior/proc/perform_idle_behavior(seconds_per_tick, datum/ai_controller/controller) - return + set waitfor = FALSE + SHOULD_CALL_PARENT(TRUE) diff --git a/code/datums/ai/idle_behaviors/idle_dog.dm b/code/datums/ai/idle_behaviors/idle_dog.dm index 46e0d040c9d..4d036e9a7a5 100644 --- a/code/datums/ai/idle_behaviors/idle_dog.dm +++ b/code/datums/ai/idle_behaviors/idle_dog.dm @@ -1,5 +1,6 @@ ///Dog specific idle behavior. /datum/idle_behavior/idle_dog/perform_idle_behavior(seconds_per_tick, datum/ai_controller/basic_controller/dog/controller) + . = ..() var/mob/living/living_pawn = controller.pawn if(!isturf(living_pawn.loc) || living_pawn.pulledby) return diff --git a/code/datums/ai/idle_behaviors/idle_haunted.dm b/code/datums/ai/idle_behaviors/idle_haunted.dm index a67b5d6cbe0..5784b5104f6 100644 --- a/code/datums/ai/idle_behaviors/idle_haunted.dm +++ b/code/datums/ai/idle_behaviors/idle_haunted.dm @@ -4,6 +4,7 @@ var/teleport_chance = 4 /datum/idle_behavior/idle_ghost_item/perform_idle_behavior(seconds_per_tick, datum/ai_controller/controller) + . = ..() var/obj/item/item_pawn = controller.pawn if(ismob(item_pawn.loc)) //Being held. dont teleport return diff --git a/code/datums/ai/idle_behaviors/idle_monkey.dm b/code/datums/ai/idle_behaviors/idle_monkey.dm index 5b5e189435d..c32534dce52 100644 --- a/code/datums/ai/idle_behaviors/idle_monkey.dm +++ b/code/datums/ai/idle_behaviors/idle_monkey.dm @@ -13,6 +13,7 @@ ) /datum/idle_behavior/idle_monkey/perform_idle_behavior(seconds_per_tick, datum/ai_controller/controller) + . = ..() var/mob/living/living_pawn = controller.pawn if(SPT_PROB(25, seconds_per_tick) && (living_pawn.mobility_flags & MOBILITY_MOVE) && isturf(living_pawn.loc) && !living_pawn.pulledby) diff --git a/tgstation.dme b/tgstation.dme index 30c3aa6177f..780c17d90de 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -712,6 +712,8 @@ #include "code\controllers\subsystem\transport.dm" #include "code\controllers\subsystem\tts.dm" #include "code\controllers\subsystem\tutorials.dm" +#include "code\controllers\subsystem\unplanned_ai_idle_controllers.dm" +#include "code\controllers\subsystem\unplanned_controllers.dm" #include "code\controllers\subsystem\verb_manager.dm" #include "code\controllers\subsystem\vis_overlays.dm" #include "code\controllers\subsystem\vote.dm"