Skip to main content

Brain System

The Brain system in Don't Starve Together provides centralized AI management for entities, coordinating behavior execution, sleep/wake cycles, and performance optimization. This system serves as the bridge between entity components and behavior trees, enabling sophisticated AI patterns while maintaining optimal game performance.

Version History

Build VersionChange DateChange TypeDescription
6753122025-06-21stableUpdated documentation to match current implementation

Overview

The brain system serves multiple purposes:

  • AI Coordination: Manages behavior tree execution for all AI entities
  • Performance Management: Implements sleep/wake cycles to optimize CPU usage
  • Event Integration: Provides event-driven behavior triggers
  • Lifecycle Management: Handles AI startup, shutdown, and cleanup

The system is built around two core classes: BrainWrangler for global management and Brain for individual entity AI control.

Core Architecture

BrainWrangler Class

The global brain manager that coordinates all AI entities:

BrainWrangler = Class(function(self)
self.instances = {} -- All brain instances
self.updaters = {} -- Brains currently updating
self._safe_updaters = {} -- Safe iteration copy
self.tickwaiters = {} -- Brains sleeping until specific tick
self.hibernaters = {} -- Hibernating brains
end)

Global Brain Manager

The system provides a singleton instance for global coordination:

BrainManager = BrainWrangler()

Brain Class

Individual brain instances that control entity AI:

Brain = Class(function(self)
self.inst = nil -- Entity this brain controls
self.currentbehaviour = nil -- Current behavior state
self.behaviourqueue = {} -- Queued behaviors
self.events = {} -- Event handlers
self.thinkperiod = nil -- Update frequency
self.lastthinktime = nil -- Last update time
self.paused = false -- Pause state
end)

Brain Management

State Management

The brain system categorizes brains into different states for optimization:

StateDescriptionPerformance Impact
UpdatersActively updating brainsHigh CPU usage
HibernatersSleeping indefinitelyMinimal CPU usage
Tick WaitersSleeping until specific timeNo CPU usage

Lifecycle Methods

Adding Brain Instances

function BrainWrangler:AddInstance(inst)
self.instances[inst] = self.updaters
self.updaters[inst] = true
end

Usage: Registers a new brain for active updating.

Removing Brain Instances

function BrainWrangler:RemoveInstance(inst)
self:SendToList(inst, nil)
-- Clean up from all lists
self.updaters[inst] = nil
self.hibernaters[inst] = nil
for k,v in pairs(self.tickwaiters) do
v[inst] = nil
end
self.instances[inst] = nil
end

Usage: Completely removes a brain from the system.

Sleep and Wake System

Wake Brain

function BrainWrangler:Wake(inst)
if self.instances[inst] then
self:SendToList(inst, self.updaters)
end
end

Usage: Moves brain from hibernation to active updating.

Hibernate Brain

function BrainWrangler:Hibernate(inst)
if self.instances[inst] then
self:SendToList(inst, self.hibernaters)
end
end

Usage: Moves brain to hibernation (stops updating).

Timed Sleep

function BrainWrangler:Sleep(inst, time_to_wait)
local sleep_ticks = time_to_wait/GetTickTime()
if sleep_ticks == 0 then sleep_ticks = 1 end

local target_tick = math.floor(GetTick() + sleep_ticks)

if target_tick > GetTick() then
local waiters = self.tickwaiters[target_tick]
if not waiters then
waiters = {}
self.tickwaiters[target_tick] = waiters
end
self:SendToList(inst, waiters)
end
end

Usage: Sleeps brain for specific duration for performance optimization.

Brain Update Cycle

Main Update Loop

The brain manager processes all brains in each game tick:

function BrainWrangler:Update(current_tick)
-- Wake up brains that finished sleeping
local waiters = self.tickwaiters[current_tick]
if waiters then
for k,v in pairs(waiters) do
self.updaters[k] = true
self.instances[k] = self.updaters
end
self.tickwaiters[current_tick] = nil
end

-- Update active brains safely
local count = 0
for k, _ in pairs(self.updaters) do
if k.inst.entity:IsValid() and not k.inst:IsAsleep() then
count = count + 1
self._safe_updaters[count] = k
end
end

for i = 1, count do
local k = self._safe_updaters[i]
self._safe_updaters[i] = nil

k:OnUpdate()
local sleep_amount = k:GetSleepTime()
if sleep_amount then
if sleep_amount > GetTickTime() then
self:Sleep(k, sleep_amount)
end
else
self:Hibernate(k)
end
end
end

Individual Brain Update

function Brain:OnUpdate()
if self.DoUpdate then
self:DoUpdate()
end

if self.bt then
self.bt:Update()
end
end

Brain Class Methods

Core Lifecycle

Start Brain

function Brain:Start()
if self.paused then
return
end

if self.OnStart then
self:OnStart()
end
self.stopped = false
BrainManager:AddInstance(self)

if self.OnInitializationComplete then
self:OnInitializationComplete()
end

-- Apply mods
if self.modpostinitfns then
for i,modfn in ipairs(self.modpostinitfns) do
modfn(self)
end
end
end

Usage: Initializes and starts brain execution.

Stop Brain

function Brain:Stop()
if self.paused then
return
end

if self.OnStop then
self:OnStop()
end
if self.bt then
self.bt:Stop()
end
self.stopped = true
BrainManager:RemoveInstance(self)
end

Usage: Stops brain execution and cleans up resources.

Pause and Resume

Pause Brain

function Brain:Pause()
self.paused = true
BrainManager:RemoveInstance(self)
end

Resume Brain

function Brain:Resume()
self.paused = false
BrainManager:AddInstance(self)
end

Performance Control

Force Update

function Brain:ForceUpdate()
if self.bt then
self.bt:ForceUpdate()
end
BrainManager:Wake(self)
end

Usage: Forces immediate brain update, bypassing sleep optimization.

Get Sleep Time

function Brain:GetSleepTime()
if self.bt then
return self.bt:GetSleepTime()
end
return 0
end

Usage: Returns optimal sleep duration for performance optimization.

Event System

Add Event Handler

function Brain:AddEventHandler(event, fn)
self.events[event] = fn
end

Push Event

function Brain:PushEvent(event, data)
local handler = self.events[event]
if handler then
handler(data)
end
end

Implementation Examples

Basic Brain Setup

-- Create a simple brain for an entity
local brain = require("brains/pigbrain")

-- Set up entity with brain
inst:AddComponent("brain")
inst.components.brain:SetBrain(brain)

-- The brain will automatically start when the entity spawns

Custom Brain Creation

-- Define a custom brain
local MyBrain = Class(Brain, function(self, inst)
Brain._ctor(self, inst)

-- Set up behavior tree
self.bt = BT(inst,
PriorityNode({
-- High priority behaviors
IfNode(function() return inst.components.health:GetPercent() < 0.3 end,
"Low Health",
ActionNode(function() inst:Flee() end, "Escape")),

-- Default behavior
ActionNode(function() inst:Wander() end, "Wander")
})
)
end)

-- Override lifecycle methods
function MyBrain:OnStart()
-- Custom initialization
print("Brain starting for", self.inst)
end

function MyBrain:OnStop()
-- Custom cleanup
print("Brain stopping for", self.inst)
end

-- Custom update logic
function MyBrain:DoUpdate()
-- Additional per-frame logic
if self.inst:IsNearPlayer() then
self.inst:PlayAnimation("alert")
end
end

return MyBrain

Event-Driven Brain

local EventBrain = Class(Brain, function(self, inst)
Brain._ctor(self, inst)

-- Set up event handlers
self:AddEventHandler("attacked", function(data)
if data.attacker and data.attacker:HasTag("player") then
self:PushEvent("player_attack", data)
end
end)

self:AddEventHandler("player_attack", function(data)
-- React to player attack
inst.components.combat:SetTarget(data.attacker)
end)

-- Behavior tree with event integration
self.bt = BT(inst,
PriorityNode({
-- Event-driven behavior
EventNode(inst, "player_attack",
SequenceNode({
ActionNode(function() inst:FaceTarget() end, "Face Attacker"),
ActionNode(function() inst:Attack() end, "Fight Back")
}),
10), -- High priority

-- Default behavior
ActionNode(function() inst:DoIdle() end, "Idle")
})
)
end)

return EventBrain

Performance-Optimized Brain

local OptimizedBrain = Class(Brain, function(self, inst)
Brain._ctor(self, inst)

-- Set up behavior tree with sleep optimization
self.bt = BT(inst,
PriorityNode({
-- Expensive checks with sleep
IfNode(function()
local result = ExpensivePathfinding()
if not result then
-- Sleep for 2 seconds if no path found
inst.components.brain:Sleep(2.0)
end
return result
end, "Pathfind",
ActionNode(function() inst:FollowPath() end, "Move")),

-- Low frequency behavior
SequenceNode({
WaitNode(5.0), -- Wait 5 seconds between checks
ConditionNode(function() return inst:NeedsFood() end, "Hungry"),
ActionNode(function() inst:FindFood() end, "Search Food")
})
}, 3.0) -- Re-evaluate priorities every 3 seconds
)
end)

-- Override to provide custom sleep logic
function OptimizedBrain:GetSleepTime()
local base_sleep = Brain.GetSleepTime(self)

-- Sleep longer when far from players
local player = FindClosestPlayer(inst, 20)
if not player then
return math.max(base_sleep, 1.0) -- Sleep at least 1 second
end

return base_sleep
end

return OptimizedBrain

Performance Optimization

Sleep Strategies

Conditional Sleep

-- Sleep based on entity state
function CustomBrain:GetSleepTime()
if self.inst:IsAsleep() then
return 5.0 -- Long sleep when entity is asleep
elseif not self.inst:HasNearbyPlayers(15) then
return 2.0 -- Medium sleep when no players nearby
else
return 0.1 -- Frequent updates when players are close
end
end

Adaptive Sleep

-- Adjust sleep time based on activity level
function AdaptiveBrain:DoUpdate()
self.activity_counter = (self.activity_counter or 0) + 1

-- Reset counter periodically
if self.activity_counter > 100 then
self.last_activity = self.activity_counter
self.activity_counter = 0
end
end

function AdaptiveBrain:GetSleepTime()
local base_sleep = Brain.GetSleepTime(self)

-- Sleep longer if brain has been inactive
if self.last_activity and self.last_activity < 10 then
return math.max(base_sleep, 3.0)
end

return base_sleep
end

Memory Management

Proper Cleanup

function CustomBrain:OnStop()
-- Clean up references
if self.cached_targets then
self.cached_targets = nil
end

if self.pathfinder then
self.pathfinder:Cancel()
self.pathfinder = nil
end

-- Call parent cleanup
Brain.OnStop(self)
end

Integration with Game Systems

Component Integration

-- Brain that works with multiple components
function ComponentBrain:OnStart()
-- Validate required components
if not self.inst.components.health then
print("Warning: Brain requires health component")
end

if not self.inst.components.locomotor then
print("Warning: Brain requires locomotor component")
end
end

function ComponentBrain:DoUpdate()
-- Use components safely
local health = self.inst.components.health
if health and health:GetPercent() < 0.5 then
self.low_health = true
else
self.low_health = false
end
end

Entity Event Integration

-- Automatically listen to entity events
function EventIntegratedBrain:OnStart()
-- Listen to entity lifecycle events
self.inst:ListenForEvent("death", function() self:Stop() end)
self.inst:ListenForEvent("attacked", function(data)
self:PushEvent("combat", data)
end)
self.inst:ListenForEvent("itemget", function(data)
self:PushEvent("inventory_change", data)
end)
end

Mod Integration

-- Brain with mod support
function ModdableBrain:OnStart()
Brain.OnStart(self)

-- Allow mods to add custom initialization
if self.mod_start_hooks then
for _, hook in ipairs(self.mod_start_hooks) do
hook(self)
end
end
end

-- Function for mods to register hooks
function ModdableBrain:AddModStartHook(fn)
self.mod_start_hooks = self.mod_start_hooks or {}
table.insert(self.mod_start_hooks, fn)
end

Debugging Support

Debug Information

function Brain:__tostring()
if self.bt then
return string.format("--brain--\nsleep time: %2.2f\n%s",
self:GetSleepTime(), tostring(self.bt))
end
return "--brain--"
end

Debug Commands

-- Console commands for debugging brains
function DebugPrintBrainStats()
print("=== Brain Statistics ===")
print("Total instances:", GetTableSize(BrainManager.instances))
print("Active updaters:", GetTableSize(BrainManager.updaters))
print("Hibernating:", GetTableSize(BrainManager.hibernaters))

local waiters = 0
for tick, list in pairs(BrainManager.tickwaiters) do
waiters = waiters + GetTableSize(list)
end
print("Sleeping (tick waiters):", waiters)
end

function DebugBrainEntity(entity)
if entity and entity.components.brain then
print("Brain info for", entity)
print(tostring(entity.components.brain.brain))
end
end

Error Handling

Safe Brain Operations

function SafeBrain:OnUpdate()
local success, error_msg = pcall(function()
if self.DoUpdate then
self:DoUpdate()
end

if self.bt then
self.bt:Update()
end
end)

if not success then
print("Brain error:", error_msg)
-- Attempt recovery or stop brain
self:Stop()
end
end

Entity Validation

function ValidatingBrain:OnUpdate()
-- Always validate entity before operations
if not self.inst or not self.inst:IsValid() then
self:Stop()
return
end

Brain.OnUpdate(self)
end

Best Practices

Design Guidelines

  • Single Responsibility: Each brain should control one entity's AI
  • Performance Awareness: Implement appropriate sleep strategies
  • Event-Driven Design: Use events for reactive behaviors
  • Component Integration: Work safely with entity components
  • Proper Cleanup: Always clean up resources in OnStop

Performance Best Practices

  • Use sleep timers for expensive operations
  • Implement hibernation for distant/inactive entities
  • Cache expensive calculations between updates
  • Avoid deep behavior tree nesting
  • Profile brain performance regularly

Maintainability

  • Document complex AI logic thoroughly
  • Use meaningful names for brain states and behaviors
  • Implement proper error handling and recovery
  • Provide debug information and visualization
  • Test brain behavior across different scenarios

Status: 🟢 Stable

The Brain system is stable and fundamental to DST's AI architecture. The API is mature and changes are rare, focusing mainly on performance optimizations and debugging improvements.