Skip to content

RemoteCrafting optimisation#142

Open
geromet wants to merge 1 commit into
SphereII:masterfrom
geromet:CsOnly
Open

RemoteCrafting optimisation#142
geromet wants to merge 1 commit into
SphereII:masterfrom
geromet:CsOnly

Conversation

@geromet

@geromet geromet commented Jul 3, 2026

Copy link
Copy Markdown

Hi, I really think this mod is essential, but was experiencing some stuttering on my (linux + steam proton) system, so me and AI agent had a look and fixed the safest parts.
Not tested in multiplayer (no frens), but caching is done per player, so it should work, while leaving room for more server optimizations.

  • Removed some more logging in hot/laggy paths. (might want to keep those for a debugging build, but during normal play, they fire at a time that is annoyingly laggy already).
  • Make dropbox scan all containers once on close, instead of once for each item.
  • Refactor, cache and optimize nearby container system.

@sonarqubecloud

sonarqubecloud Bot commented Jul 3, 2026

Copy link
Copy Markdown


if (__instance.localTileEntity == null)
{
Log.Out("[DropBox] OnClose: localTileEntity is null, skipping.");

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (!buildsettings.Development)
Log.Out
?

var composite = __instance.localTileEntity as TEFeatureStorage;
if (composite == null)
{
Log.Out("[DropBox] OnClose: localTileEntity is not TEFeatureStorage, skipping.");

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (!buildsettings.Development)
Log.Out
?

StringParsers.TryParseBool(dropBoxStr, out var dropBoxBool) && dropBoxBool;
if (block is not BlockDropBoxContainer && !hasDropBoxProperty)
{
Log.Out($"[DropBox] OnClose: block at {pos} ({block.GetBlockName()}) is not a drop box, skipping.");

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (!buildsettings.Development)
Log.Out
?

distance = StringParsers.ParseFloat(strDistance);

var primaryPlayer = __instance.xui.playerUI.entityPlayer;
Log.Out($"[DropBox] OnClose: distributing from {block.GetBlockName()} at {pos}, player={primaryPlayer?.EntityName ?? "null"}, distance={distance}, IsServer={SingletonMonoBehaviour<ConnectionManager>.Instance.IsServer}");

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (!buildsettings.Development)
Log.Out
?


if (__instance.localTileEntity.items == null)
{
Log.Out("[DropBox] OnClose: items array is null, skipping.");

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (!buildsettings.Development)
Log.Out
?

}

var items = __instance.localTileEntity.items;
var tileEntities = RemoteCraftingUtils.GetTileEntities(primaryPlayer, distance, false);

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Get tileEntities once before loop instead of repeatedly for each item.

{
if (items[i].IsEmpty()) continue;
var itemName = items[i].itemValue?.ItemClass?.GetItemName() ?? "unknown";
Log.Out($"[DropBox] OnClose: trying to distribute slot {i}: {itemName} x{items[i].count}");

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (!buildsettings.Development)
Log.Out
?

var itemName = items[i].itemValue?.ItemClass?.GetItemName() ?? "unknown";
Log.Out($"[DropBox] OnClose: trying to distribute slot {i}: {itemName} x{items[i].count}");

if (RemoteCraftingUtils.AddToNearbyContainer(primaryPlayer, items[i], distance))

@geromet geromet Jul 8, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use new function overload to use cached tileEntitites instead of recalculating them for each item.

if (RemoteCraftingUtils.AddToNearbyContainer(primaryPlayer, items[i], distance))
if (RemoteCraftingUtils.AddToNearbyContainer(primaryPlayer, items[i], tileEntities))
{
Log.Out($"[DropBox] OnClose: distributed {itemName} successfully, clearing slot {i}.");

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (!buildsettings.Development)
Log.Out
?

continue;
}

Log.Out($"[DropBox] OnClose: no container accepted {itemName}, leaving in slot {i}.");

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (!buildsettings.Development)
Log.Out
?


private static NearbyContainerCacheEntry _nearbyContainerCache;

private sealed class NearbyContainerCacheEntry

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cache nearby containers to reduce frequency of recalculating them.
Invalidates on item change, or player moving out of the positionBucket.
Refactors other code touched to integrate cleaner, and be more readable. Code already had comments about needing to be refactored. Public signatures that have uses inside the project were not altered, but instead overloads/wrappers were added.

}
}

private sealed class NearbyContainerScanContext

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put configuration in one context.
Mainly because previously this was re-read from files inside a hot loop.
But this also helps with separating business logic, from previously "magic" looking string manipulation.

var disabledsender = Configuration.GetPropertyValue(AdvFeatureClass, "disablesender").Split(',');
var nottoWorkstation = Configuration.GetPropertyValue(AdvFeatureClass, "nottoWorkstation");
var bindtoWorkstation = Configuration.GetPropertyValue(AdvFeatureClass, "bindtoWorkstation");
var scanContext = BuildNearbyContainerScanContext(player, landClaimContainersOnly, landClaimPlayerOnly);

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use context and cache instead of magic and Configuration.GetPropertyValue.

continue;
}
if (!tileEntity.TryGetSelfOrFeature<ITileEntityLootable>(out var lootTileEntity))
if (!ShouldIncludeTileEntity(scanContext, tileEntity))

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wall of guard clauses into one boolean.

continue;
}
}
tileEntities.Add(tileEntity);

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should include. Add it.

currentWorkstation = GetCurrentWorkstation(playerLocal);
}
else
catch

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This workstation has an issue. Haven't encountered this in game, but worth adding logging for a test build.

return value?.Split(',') ?? Array.Empty<string>();
}

private static List<WorkstationLootBinding> ParseWorkstationBindings(string value, bool trimLootLists)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All the magic, mostly in one function.

return values;
}

private static bool ShouldIncludeTileEntity(NearbyContainerScanContext scanContext, TileEntity tileEntity)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previous wall of guard clauses

return string.IsNullOrEmpty(scanContext.BindToWorkstationRaw) || BindToWorkstation(scanContext, tileEntity);
}

public static bool DisableSender(IEnumerable<string> value, ITileEntity tileEntity)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Compatibility wrapper

var disableSenderValues = value as string[] ?? value.ToArray();
var invertdisable = bool.Parse(Configuration.GetPropertyValue(AdvFeatureClass, "Invertdisable"));
if (!invertdisable)
return DisableSender(disableSenderValues, invertdisable, tileEntity);

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once per scan: read config, parse bool, normalize list
for every tile entity: only run the actual check.
Cached in scanContext

if (player is not EntityPlayerLocal playerLocal) return false;
if (!scanContext.HasLocalWorkstationContext) return false;
if (!tileEntity.TryGetSelfOrFeature<ITileEntityLootable>(out var lootTileEntity)) return false;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored to de-complexify the complex LinQ

{
var result = false;
if (player is not EntityPlayerLocal playerLocal) return false;
if (!scanContext.HasLocalWorkstationContext) return false;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice clarity benefit of refactor.

if (!tileEntity.TryGetSelfOrFeature<ITileEntityLootable>(out var lootTileEntity)) return false;

foreach (var bind in value.Split(';'))
foreach (var binding in scanContext.NotToWorkstationBindings)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We know what we are doing with what now.

{
var tileEntities = GetTileEntities(player, distance, false);
var itemName = itemStack.itemValue?.ItemClass?.GetItemName() ?? "unknown";
return AddToNearbyContainer(player, itemStack, tileEntities);

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Compat

return AddToNearbyContainer(player, itemStack, tileEntities);
}

public static bool AddToNearbyContainer(EntityAlive player, ItemStack itemStack, IEnumerable<TileEntity> tileEntities)

@geromet geromet Jul 8, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

O(IC + IK) -> O(C + IK)

break;
}
}
// Remove only the amount this container can actually satisfy, then continue to the next source.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

O(n)->O(1)
Clarity
No off by one bugs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant