Skip to content
RustPluginGenerator
Runtime errorOxide + Carbon

Fix "Calling 'OnEntitySpawned' ... took Nms" hook performance warning in Oxide

Oxide prints this line when a hook takes 100ms or longer, so the hook is doing too much work on the main thread. The fix is to stop scanning every spawned entity inside the hook and instead maintain a running count that you update incrementally.

Server console
Calling 'OnEntitySpawned' on 'EntityLimits v1.0.0' took 412ms

Why it happens

Oxide times every hook call and logs Calling '<Hook>' on '<Name> v<Version>' took <N>ms once the call passes 100ms. OnEntitySpawned fires for every networkable that is spawned, so during a server start, a wave of animals, or a large base being placed it can run dozens of times per second. A handler that loops BaseNetworkable.serverEntities on each call is quadratic: one spawn walks the whole entity table, and the next spawn walks it again. 412ms for a single call means thousands of entities were iterated in that one callback, and every other hook waits behind it because Rust and Oxide run on the main thread. The same warning appears with a [GARBAGE COLLECT] suffix when the allocation pressure from the loop forced a GC during the call.

How to fix it

  1. Open the plugin and find the OnEntitySpawned body. Replace the foreach (var entity in BaseNetworkable.serverEntities) loop with a counter you keep in memory.
  2. For player-made deployables, keep a dictionary keyed by OwnerID and increment it when the spawned entity is a BaseCombatEntity or BuildingBlock owned by a player; decrement it in OnEntityKill.
  3. If you need an absolute number for a limit check, recompute it rarely (on load, on a 60-second timer, or on demand from a console command) instead of on every spawn.
  4. Reset the cache in Unload() and save it to oxide/data only if the count must survive a restart.
  5. Run oxide.reload EntityLimits, spawn several entities, and watch the console: the took ...ms line should no longer appear for OnEntitySpawned.

Before and after

BeforeFails
using System.Collections.Generic;
using Oxide.Core.Plugins;

namespace Oxide.Plugins
{
    [Info("EntityLimits", "RustPluginGenerator", "1.0.0")]
    [Description("Blocks deploys when a player exceeds the entity limit.")]
    public class EntityLimits : RustPlugin
    {
        private const int MaxDeployables = 200;

        private void OnEntitySpawned(BaseNetworkable entity)
        {
            var deployable = entity as BaseCombatEntity;
            if (deployable == null || deployable.OwnerID == 0) return;

            // Counts every entity on the server for each single spawn.
            int count = 0;
            foreach (var networkable in BaseNetworkable.serverEntities)
            {
                var other = networkable as BaseCombatEntity;
                if (other == null) continue;
                if (other.OwnerID == deployable.OwnerID) count++;
            }

            if (count > MaxDeployables)
            {
                PrintWarning("Player " + deployable.OwnerID + " exceeded the entity limit");
                deployable.Kill();
            }
        }
    }
}
AfterFixed
using System;
using System.Collections.Generic;
using Newtonsoft.Json;

namespace Oxide.Plugins
{
    [Info("EntityLimits", "RustPluginGenerator", "1.0.1")]
    [Description("Blocks deploys when a player exceeds the entity limit.")]
    public class EntityLimits : RustPlugin
    {
        private const int MaxDeployables = 200;

        private PluginConfig config;
        private readonly Dictionary<ulong, int> counts = new Dictionary<ulong, int>();

        private class PluginConfig
        {
            [JsonProperty("Maximum deployables per player")]
            public int MaxDeployables = 200;
        }

        protected override void LoadDefaultConfig() => config = new PluginConfig();

        protected override void LoadConfig()
        {
            base.LoadConfig();
            try { config = Config.ReadObject<PluginConfig>(); if (config == null) throw new System.Exception(); }
            catch { PrintWarning("Configuration file is invalid, using defaults"); LoadDefaultConfig(); }
            SaveConfig();
        }

        protected override void SaveConfig() => Config.WriteObject(config);

        private void OnEntitySpawned(BaseNetworkable entity)
        {
            var deployable = entity as BaseCombatEntity;
            if (deployable == null || deployable.OwnerID == 0) return;

            int count;
            counts.TryGetValue(deployable.OwnerID, out count);
            count++;
            counts[deployable.OwnerID] = count;

            if (count > config.MaxDeployables)
            {
                PrintWarning("Player " + deployable.OwnerID + " exceeded the entity limit");
                deployable.Kill();
                counts[deployable.OwnerID] = count - 1;
            }
        }

        private void OnEntityKill(BaseNetworkable entity)
        {
            var deployable = entity as BaseCombatEntity;
            if (deployable == null || deployable.OwnerID == 0) return;

            int count;
            if (counts.TryGetValue(deployable.OwnerID, out count))
            {
                if (count <= 1) counts.Remove(deployable.OwnerID);
                else counts[deployable.OwnerID] = count - 1;
            }
        }

        private void Unload()
        {
            counts.Clear();
        }
    }
}

FAQ

Where is the 100ms threshold documented?

It is not a config option. Oxide measures each hook call and only writes the Calling ... took ...ms line when the call exceeds 100 milliseconds, so any plugin you see in that output is blocking the server thread for at least that long.

What does the [GARBAGE COLLECT] suffix mean?

It means a garbage collection ran while your hook was executing. That usually points to allocations inside the hook, such as building temporary lists or LINQ queries over serverEntities on every spawn. Removing the allocations usually removes the warning too.

Is a warning like this dangerous for the server?

Hooks run on the main thread, so the server cannot process player input or other plugins until the hook returns. One 412ms call is a visible stutter for everyone; repeated calls during a raid or wipe can cause rubber-banding and dropped connections.

Does Carbon print the same warning?

Carbon loads Oxide plugins and runs the same hooks, so the effect is the same. Carbon's own timing output is formatted differently, but the cause is always heavy work inside the hook, not the framework.

Different error?

Paste the full console output and get the cause and a fix in plain English.

Open the error explainer

Skip the debugging

Generated plugins are compiled before you get them, and compiler errors are fixed automatically.

Generate a plugin

2 free plugins a month, no card.

Last updated Oct 26, 2026.

Get plugins that compile first time

Generate