groupsCommunity
Performance Best Practices
s&box Performance Best Practices
Community guide for writing performant s&box code based on engine behavior and developer recommendations.
Object Pooling
Avoid frequent new GameObject() and Destroy() calls:
CSHARP
public class BulletPool : Component
{
private Queue<Bullet> _pool = new();
private const int POOL_SIZE = 50;
void InitializePool()
{
for (int i = 0; i < POOL_SIZE; i++)
{
var bullet = new GameObject();
bullet.Components.Create<Bullet>();
bullet.Enabled = false;
_pool.Enqueue(bullet.Components.Get<Bullet>());
}
}
public Bullet GetBullet()
{
if (_pool.Count > 0)
{
var bullet = _pool.Dequeue();
bullet.GameObject.Enabled = true;
return bullet;
}
// Fallback: create new (but warn)
Log.Warning("Bullet pool exhausted!");
var newBullet = new GameObject();
return newBullet.Components.Create<Bullet>();
}
public void ReturnBullet(Bullet bullet)
{
bullet.GameObject.Enabled = false;
_pool.Enqueue(bullet);
}
}Component Caching Patterns
Cache at startup, never in Update:
CSHARP
public class Weapon : Component
{
// Cache all needed components
private ModelRenderer _renderer;
private SoundPointComponent _sound;
private List<Attachment> _attachments = new();
protected override void OnAwake()
{
// One-time lookup
_renderer = Components.Get<ModelRenderer>();
_sound = Components.Get<SoundPointComponent>();
_attachments = Components.GetAll<Attachment>().ToList();
}
}Scene Query Optimization
Minimize queries in hot paths:
CSHARP
public class EnemySpawner : Component
{
private List<GameObject> _cachedEnemies = new();
private RealTimeSince _lastCacheUpdate;
protected override void OnUpdate()
{
// Refresh cache every second, not every frame
if (_lastCacheUpdate > 1.0f)
{
_cachedEnemies = Scene.FindAllObjectsWithTag("enemy").ToList();
_lastCacheUpdate = 0;
}
// Use cached list
foreach (var enemy in _cachedEnemies)
{
if (!enemy.IsValid()) continue;
UpdateEnemy(enemy);
}
}
}String and Allocation Reduction
CSHARP
public class HUD : Component
{
// ❌ Bad: Allocates every frame
protected override void OnUpdate()
{
ScoreLabel.Text = $"Score: {Score}";
}
// ✅ Good: Only update when changed
private int _lastScore = -1;
protected override void OnUpdate()
{
if (Score != _lastScore)
{
ScoreLabel.Text = $"Score: {Score}";
_lastScore = Score;
}
}
}Physics Optimization
Use layers and collision rules:
CSHARP
void OptimizedTrace()
{
// ✅ Good: Use collision rules instead of checking tags after
var tr = Scene.Trace
.Ray(start, end)
.WithCollisionRules("weapon", "player")
.WithoutTag("trigger", "decal")
.Run();
}Networking Efficiency
CSHARP
public class PlayerState : Component
{
// ✅ Good: Batch sync properties, use interpolation
[Sync(SyncFlags.Interpolate)]
public Vector3 Position { get; set; }
[Sync]
public int Health { get; set; }
// ✅ Good: Rate-limit network updates
private RealTimeSince _lastHealthUpdate;
void UpdateHealth(int newHealth)
{
if (_lastHealthUpdate < 0.1f) return; // Max 10 updates/sec
Health = newHealth;
_lastHealthUpdate = 0;
}
}UI Performance
CSHARP
// ✅ Good: Use BuildHash to prevent rebuilds
public class ScorePanel : Panel
{
public int Score { get; set; }
// Panel only rebuilds when hash changes
public override int BuildHash() => Score.GetHashCode();
}Source
- Official optimization docs: https://sbox.game/dev/doc
- Facepunch developer blog posts
- Community benchmark results
Was this helpful?