Class
Version History
Build Version | Change Date | Change Type | Description |
---|---|---|---|
676042 | 2025-06-21 | stable | Current version |
Overview
The Class system provides object-oriented programming capabilities for Don't Starve Together's Lua environment. It implements inheritance, property management with setters/getters, read-only properties, instance tracking, and hot reloading support. This system enables structured code organization and reusable component patterns throughout the game.
The Class system is compatible with Lua 5.1 and uses metatables to implement inheritance and method dispatch. It supports single inheritance, constructor functions, property validation, and optional instance tracking for memory debugging.
Usage Example
-- Simple class with constructor
local Player = Class(function(self, name, health)
self.name = name
self.health = health or 100
self.inventory = {}
end)
-- Class with inheritance
local Warrior = Class(Player, function(self, name, health, weapon)
Player._ctor(self, name, health) -- Call parent constructor
self.weapon = weapon or "sword"
self.armor = 50
end)
-- Create instances
local player = Player("Wilson", 150)
local warrior = Warrior("Wigfrid", 200, "spear")
-- Check inheritance
print(player:is_a(Player)) -- true
print(warrior:is_a(Player)) -- true (inheritance)
print(warrior:is_a(Warrior)) -- true
Functions
Class(base, _ctor, props)
Status: stable
Description: Creates a new class with optional inheritance, constructor, and property management.
Parameters:
base
(table/function, optional): Base class for inheritance, or constructor if no inheritance_ctor
(function, optional): Constructor function called when creating instancesprops
(table, optional): Property definitions with setter functions
Returns:
- (table): New class object with metatable configured for inheritance
Example:
-- Simple class
local Animal = Class(function(self, name)
self.name = name
self.health = 100
end)
-- Class with inheritance
local Dog = Class(Animal, function(self, name, breed)
Animal._ctor(self, name)
self.breed = breed
end)
-- Class with property management
local Monster = Class(function(self, name)
self.name = name
self._health = 100
end, {
health = function(self, value, old)
if value < 0 then value = 0 end
if value > self.max_health then value = self.max_health end
self._health = value
if value <= 0 then
self:OnDeath()
end
end
})
Version History:
- Added in initial implementation
- Supports Lua 5.1 compatibility
makereadonly(t, k)
Status: stable
Description: Makes a property read-only, preventing modification after initial assignment.
Parameters:
t
(table): Instance to modifyk
(string): Property name to make read-only
Returns:
- (void): No return value
Example:
local Config = Class(function(self, version)
self.version = version
self.debug_mode = false
-- Make version read-only after creation
makereadonly(self, "version")
end)
local config = Config("1.0.0")
print(config.version) -- "1.0.0"
-- This will throw an error:
-- config.version = "2.0.0" -- Error: Cannot change read only property
Version History:
- Added in initial implementation
addsetter(t, k, fn)
Status: stable
Description: Adds a setter function that validates or transforms property assignments.
Parameters:
t
(table): Instance to modifyk
(string): Property namefn
(function): Setter function with signature(self, new_value, old_value)
Returns:
- (void): No return value
Example:
local Character = Class(function(self, name)
self.name = name
self._health = 100
self.max_health = 100
-- Add health validation
addsetter(self, "health", function(self, value, old)
-- Clamp value between 0 and max_health
value = math.max(0, math.min(value, self.max_health))
self._health = value
-- Trigger events
if value <= 0 and old > 0 then
self:OnDeath()
elseif value > old then
self:OnHeal(value - old)
end
end)
end)
local player = Character("Wilson")
player.health = 150 -- Automatically clamped to 100
player.health = -10 -- Automatically clamped to 0, triggers OnDeath
Version History:
- Added in initial implementation
removesetter(t, k)
Status: stable
Description: Removes a property setter, restoring direct property access.
Parameters:
t
(table): Instance to modifyk
(string): Property name
Returns:
- (void): No return value
Example:
-- Remove the health setter for direct access
removesetter(player, "health")
player.health = 75 -- Now sets directly without validation
Version History:
- Added in initial implementation
Instance Methods
inst:is_a(klass)
Status: stable
Description: Checks if an instance is derived from a specific class.
Parameters:
klass
(table): Class to check inheritance against
Returns:
- (boolean):
true
if instance inherits from the class
Example:
local player = Player("Wilson", 150)
local warrior = Warrior("Wigfrid", 200, "spear")
print(player:is_a(Player)) -- true
print(warrior:is_a(Player)) -- true (inheritance)
print(warrior:is_a(Warrior)) -- true
print(player:is_a(Warrior)) -- false
Version History:
- Added in initial implementation
inst:is_class()
Status: stable
Description: Checks if an object is a class definition rather than an instance.
Parameters:
- None
Returns:
- (boolean):
true
if object is a class,false
if instance
Example:
print(Player:is_class()) -- true
print(player:is_class()) -- false
-- Useful for validation
local function ProcessObject(obj)
if obj:is_class() then
print("This is a class definition")
else
print("This is an instance")
end
end
Version History:
- Added in initial implementation
Class:is_instance(obj)
Status: stable
Description: Class method to check if an object is an instance of this specific class.
Parameters:
obj
(any): Object to test
Returns:
- (boolean):
true
if object is an instance of this class
Example:
local player = Player("Wilson")
local number = 42
print(Player:is_instance(player)) -- true
print(Player:is_instance(number)) -- false
print(Warrior:is_instance(player)) -- false (different class)
Version History:
- Added in initial implementation
Global Configuration
TrackClassInstances
Value: false
Status: stable
Description: Enable/disable instance tracking for debugging purposes.
Example:
-- Enable in class.lua for development
local TrackClassInstances = true
Version History:
- Added in initial implementation
ClassRegistry
Type: table
Status: stable
Description: Registry of all defined classes for hot reloading support.
Version History:
- Added in initial implementation
ClassTrackingTable
Type: table
Status: stable
Description: Weak-keyed table tracking instances per class when tracking is enabled.
Version History:
- Added in initial implementation
ClassTrackingInterval
Value: 100
Status: stable
Description: Frames between tracking reports when instance tracking is enabled.
Version History:
- Added in initial implementation
Advanced Functions
HandleClassInstanceTracking()
Status: stable
Description: Provides periodic reports on class instance counts for memory debugging. Must be called from main game loop when tracking is enabled.
Parameters:
- None
Returns:
- (void): No return value
Example:
-- Enable tracking (set at top of class.lua)
local TrackClassInstances = true
ClassTrackingInterval = 60 -- Report every 60 frames
-- In main game loop
function Update(dt)
HandleClassInstanceTracking()
-- ... other update logic
end
-- Output will show top 10 classes by instance count:
-- 1 : Monster - 1500
-- 2 : Component - 800
-- 3 : Player - 4
Version History:
- Added in initial implementation
ReloadedClass(mt)
Status: stable
Description: Cleans up class registry during hot reloading to prevent memory leaks.
Parameters:
mt
(table): Class metatable to remove from registry
Returns:
- (void): No return value
Example:
-- Called automatically during mod hot reload
-- Removes old class definitions from registry
Version History:
- Added in initial implementation
Common Usage Patterns
Basic Inheritance
-- Base entity class
local Entity = Class(function(self, x, y)
self.x = x or 0
self.y = y or 0
self.components = {}
end)
function Entity:AddComponent(name, component)
self.components[name] = component
component.inst = self
end
-- Living entity with health
local Living = Class(Entity, function(self, x, y, health)
Entity._ctor(self, x, y)
self.health = health or 100
self.max_health = health or 100
end)
function Living:TakeDamage(amount)
self.health = self.health - amount
if self.health <= 0 then
self:Die()
end
end
Property Validation
local ValidatedCharacter = Class(function(self, name)
self.name = name
self._level = 1
self._health = 100
-- Level validation (1-100)
addsetter(self, "level", function(self, value, old)
if type(value) ~= "number" then
error("Level must be a number")
end
value = math.max(1, math.min(100, math.floor(value)))
self._level = value
-- Scale health with level
self.max_health = 100 + (value * 10)
end)
-- Health validation
addsetter(self, "health", function(self, value, old)
value = math.max(0, math.min(value, self.max_health))
self._health = value
if value <= 0 then
self:OnDeath()
end
end)
end)
Component System
-- Base component class
local Component = Class(function(self)
self.inst = nil -- Will be set when added to entity
end)
-- Health component
local Health = Class(Component, function(self, max_health)
Component._ctor(self)
self.max_health = max_health or 100
self.current_health = self.max_health
end)
function Health:TakeDamage(amount)
self.current_health = math.max(0, self.current_health - amount)
if self.current_health <= 0 then
self.inst:PushEvent("death")
end
self.inst:PushEvent("healthdelta", {old = self.current_health + amount, new = self.current_health})
end
Implementation Details
The Class system uses several Lua metatable features:
__index
: For method lookup and property getters__newindex
: For property setters and validation__call
: For constructor invocation- Weak references: For memory-safe instance tracking
Metatable Structure
-- Class metatable provides inheritance chain
local c = {} -- Class table
c._base = base_class -- Parent class reference
c.__index = c -- Method lookup
c._ctor = constructor -- Constructor function
-- Instance tracking (when enabled)
ClassTrackingTable[mt] = {} -- Weak-keyed instance table
Related Modules
- Components: Component-based entity architecture built on Class system
- Prefabs: Entity creation using Class-based components
- EntityScript: Core entity functionality using Class inheritance
- Mod System: Hot reloading support for class modifications
Technical Notes
- Lua Version: Compatible with Lua 5.1, not compatible with Lua 5.0
- Memory Management: Uses weak references for instance tracking to prevent memory leaks
- Performance: Minimal overhead for method dispatch through metatable chains
- Inheritance: Single inheritance only, no multiple inheritance support
- Hot Reloading: Full support for class redefinition during development
Best Practices
Constructor Patterns
-- Good: Clear parameter validation
local Character = Class(function(self, name, health, level)
assert(type(name) == "string", "Name must be a string")
assert(type(health) == "number", "Health must be a number")
self.name = name
self.health = math.max(1, health)
self.level = level or 1
end)
-- Good: Default parameter handling
local Monster = Class(function(self, config)
config = config or {}
self.health = config.health or 100
self.damage = config.damage or 10
self.speed = config.speed or 1
end)
Inheritance Guidelines
-- Good: Always call parent constructor
local Derived = Class(Base, function(self, ...)
Base._ctor(self, ...) -- Call parent first
-- Then initialize derived-specific properties
end)
-- Good: Override methods properly
function Derived:SomeMethod(...)
-- Call parent method if needed
local result = Base.SomeMethod(self, ...)
-- Add derived behavior
-- ... additional logic
return result
end
Property Management
-- Good: Use setters for validation
local ValidatedClass = Class(function(self)
self._private_value = 0
addsetter(self, "value", function(self, new_val, old_val)
if type(new_val) ~= "number" then
error("Value must be a number")
end
self._private_value = math.max(0, new_val)
end)
end)
-- Good: Provide getter methods for computed properties
function ValidatedClass:GetValue()
return self._private_value
end
function ValidatedClass:GetDisplayValue()
return string.format("%.2f", self._private_value)
end