This is a long read, but bear with me. I'm not asking for a feature, just reporting my experience from some testing I did and looking for your thoughts.
We're a small group (3-5 players usually) and play on a dedicated server. I like to grab the latest source and compile the missions, so we're pretty much on the bleeding edge. I noticed that recently the performance seems to have worsened a bit. It usually manifests in the AI moving in a laggy way and dying with quite a bit of delay after being shot, and actions like transferring items between inventories taking longer. Naturally, all of this was caused by the server FPS dropping badly during these moments (to about 1-2 for a couple of seconds, then going up to a measly 10-15 before stabilizing at around 30). It seems especially bad on the Livonia terrain, but is generally observable everywhere.
So I went looking through the code for anything that might cause it. My first guess was a bad while
loop somewhere that doesn't sleep between iterations, but I didn't find a single one, the code is very solid. The second option I looked into was AI. To verify that theory, I commented out the initialization of ambient infantry, traffic and so on. This resulted in excellent performance with server FPS at 50. So it looks like there's just too much infantry active at some points in the mission and whenever a group spawns or is "triggered" by an engagement, the server gets overloaded. The main issue is that all the AI computation in Arma seems to be done in a single thread. That's also something I can observe on my server, because a single core is working at a 100% while the remaining ones are barely doing anything in comparison. At that point I thought I had hit a dead end, because we can't lower the number of simultaneously active AI without affecting the gameplay, all of their operations running in a single thread is an engine limitation, and I didn't see how this could be easily offloaded to a HC, because AI spawning is not handled by a single script, but several.
diff --git a/functions/DRN/fn_AmbientInfantry.sqf b/functions/DRN/fn_AmbientInfantry.sqf
index 6305454..c52091d 100644
--- a/functions/DRN/fn_AmbientInfantry.sqf
+++ b/functions/DRN/fn_AmbientInfantry.sqf
@@ -15,17 +15,19 @@
* [_garbageCollectDistance]: Dead units at this distance from _referenceGroup will be deleted.
* [_fnc_OnSpawnUnit]: Code run once for every spawned unit (after the whole group is created). The unit can be accessed through "_this". Default value is {}.
* [_fnc_OnSpawnGroup]: Code run once for every spawned group (after the whole group is created). The group can be accessed through "_this". Default value is {};
+ * [_enemyFrequency]: Only required for calling UnitClasses.sqf.
* [A3E_Debug]: true if debugmessages and areas will be shown for player. Default false.
* Dependencies: CommonLib v1.01
*/
-if (!isServer) exitWith {};
+if (isServer || hasInterface) exitWith {};
private ["_referenceGroup", "_side", "_groupsCount", "_minSpawnDistance", "_maxSpawnDistance", "_infantryClasses", "_minSkill", "_maxSkill", "_garbageCollectDistance"];
private ["_activeGroups", "_activeUnits", "_spawnPos", "_group", "_possibleInfantryTypes", "_infantryType", "_minDistance", "_skill", "_vehicleVarName", "_factionsArray"];
private ["_minUnitsInGroup", "_maxUnitsInGroup", "_i", "_atScriptStartUp", "_currentEntityNo", "_DebugMsg", "_farAwayUnits", "_farAwayUnitsCount", "_unitsToDeleteCount", "_groupsToDeleteCount"];
private ["_DebugMarkers", "_DebugMarkerNo", "_DebugMarkerName", "_isFaction", "_unitsToDelete", "_groupsToDelete", "_tempGroups", "_tempGroupsCount"];
private ["_fnc_OnSpawnUnit", "_fnc_OnSpawnGroup"];
+private ["_enemyFrequency"];
_referenceGroup = _this select 0;
_side = _this select 1;
@@ -40,7 +42,10 @@ if (count _this > 9) then {_maxSkill = _this select 9;} else {_maxSkill = 0.6;};
if (count _this > 10) then {_garbageCollectDistance = _this select 10;} else {_garbageCollectDistance = 750;};
if (count _this > 11) then {_fnc_OnSpawnUnit = _this select 11;} else {_fnc_OnSpawnUnit = {};};
if (count _this > 12) then {_fnc_OnSpawnGroup = _this select 12;} else {_fnc_OnSpawnGroup = {};};
+if (count _this > 13) then {_enemyFrequency = _this select 13;} else {_enemyFrequency = 2;};
+// Need to do this again, because we need its global variables and it is only executed on the server
+[_enemyFrequency] call compile preprocessFileLineNumbers "Units\UnitClasses.sqf";
//WHY!?!?!?!?!
_factionsArray = [A3E_VAR_Side_Ind , A3E_VAR_Side_Ind , A3E_VAR_Side_Ind , A3E_VAR_Side_Ind , A3E_VAR_Side_Ind , A3E_VAR_Side_Ind , A3E_VAR_Side_Opfor , A3E_VAR_Side_Opfor , A3E_VAR_Side_Opfor , A3E_VAR_Side_Opfor ,A3E_VAR_Side_Opfor];
diff --git a/functions/DRN/fn_MilitaryTraffic.sqf b/functions/DRN/fn_MilitaryTraffic.sqf
index 5f9b1bd..2c477b7 100644
--- a/functions/DRN/fn_MilitaryTraffic.sqf
+++ b/functions/DRN/fn_MilitaryTraffic.sqf
@@ -1,3 +1,5 @@
+if (isServer || hasInterface) exitWith {};
+
private ["_activeVehiclesAndGroup", "_vehiclesGroup", "_spawnSegment", "_vehicle", "_group", "_result", "_possibleVehicles", "_vehicleType", "_vehiclesCrew", "_skill", "_minDistance", "_tries", "_trafficLocation"];
private ["_currentEntityNo", "_vehicleVarName", "_tempVehiclesAndGroup", "_deletedVehiclesCount", "_firstIteration", "_roadSegments", "_destinationSegment", "_destinationPos", "_direction"];
private ["_roadSegmentDirection", "_testDirection", "_facingAway", "_posX", "_posY", "_pos"];
diff --git a/functions/Server/fn_initServer.sqf b/functions/Server/fn_initServer.sqf
index d3066ac..f24111a 100644
--- a/functions/Server/fn_initServer.sqf
+++ b/functions/Server/fn_initServer.sqf
@@ -327,7 +327,7 @@ private _UseMotorPools = Param_MotorPools;
_radius = (_enemySpawnDistance + 500) / 1000;
_infantryGroupsCount = round (_groupsPerSqkm * _radius * _radius * 3.141592);
- [_playerGroup, A3E_VAR_Side_Opfor, a3e_arr_Escape_InfantryTypes, _infantryGroupsCount, _enemySpawnDistance + 200, _enemySpawnDistance + 500, _minEnemiesPerGroup, _maxEnemiesPerGroup, _enemyMinSkill, _enemyMaxSkill, 750, _fnc_OnSpawnAmbientInfantryUnit, _fnc_OnSpawnAmbientInfantryGroup, A3E_Debug] spawn drn_fnc_AmbientInfantry;
+ [_playerGroup, A3E_VAR_Side_Opfor, a3e_arr_Escape_InfantryTypes, _infantryGroupsCount, _enemySpawnDistance + 200, _enemySpawnDistance + 500, _minEnemiesPerGroup, _maxEnemiesPerGroup, _enemyMinSkill, _enemyMaxSkill, 750, _fnc_OnSpawnAmbientInfantryUnit, _fnc_OnSpawnAmbientInfantryGroup, _enemyFrequency, A3E_Debug] remoteExec ["drn_fnc_AmbientInfantry"];
// Initialize the Escape military and civilian traffic
@@ -417,7 +417,7 @@ private _UseMotorPools = Param_MotorPools;
};
};
- [civilian, [], _vehiclesCount, _enemySpawnDistance, _radius, 0.5, 0.5, _fnc_onSpawnCivilian, A3E_Debug] spawn drn_fnc_MilitaryTraffic;
+ [civilian, [], _vehiclesCount, _enemySpawnDistance, _radius, 0.5, 0.5, _fnc_onSpawnCivilian, A3E_Debug] remoteExec ["drn_fnc_MilitaryTraffic"];
// Enemy military traffic
@@ -443,8 +443,8 @@ private _UseMotorPools = Param_MotorPools;
[_vehiclesCount,_enemySpawnDistance,_radius,_enemyMinSkill, _enemyMaxSkill] spawn {
params["_vehiclesCount","_enemySpawnDistance","_radius","_enemyMinSkill", "_enemyMaxSkill"];
sleep 60*15; //Wait 15 Minutes before heavy vehicles may arrive
- [A3E_VAR_Side_Opfor, [], _vehiclesCount/2, _enemySpawnDistance, _radius, _enemyMinSkill, _enemyMaxSkill, drn_fnc_Escape_TrafficSearch, A3E_Debug] spawn drn_fnc_MilitaryTraffic;
- [A3E_VAR_Side_Ind, [], _vehiclesCount/2, _enemySpawnDistance, _radius, _enemyMinSkill, _enemyMaxSkill, drn_fnc_Escape_TrafficSearch, A3E_Debug] spawn drn_fnc_MilitaryTraffic;
+ [A3E_VAR_Side_Opfor, [], _vehiclesCount/2, _enemySpawnDistance, _radius, _enemyMinSkill, _enemyMaxSkill, drn_fnc_Escape_TrafficSearch, A3E_Debug] remoteExec ["drn_fnc_MilitaryTraffic"];
+ [A3E_VAR_Side_Ind, [], _vehiclesCount/2, _enemySpawnDistance, _radius, _enemyMinSkill, _enemyMaxSkill, drn_fnc_Escape_TrafficSearch, A3E_Debug] remoteExec ["drn_fnc_MilitaryTraffic"];
};
private ["_areaPerRoadBlock", "_maxEnemySpawnDistanceKm", "_roadBlockCount"];
I noticed no adverse effects on gameplay so far, and performance is simply fantastic. The server now runs consistently at 48-50 FPS, with a few drops down to 30 in some situations, but never below that. But since most of the AI now runs on the HC, server FPS aren't relevant to their performance anymore, now it's how busy the HC is that determines this. And it's looking really good. Instead of seeing a single core scrambling to keep up, the load is much more balanced now.
It's still possible to overload it slightly and make the AI lag for an instant when driving at speed, but only in very few instances. And that makes sense, because as long as whatever core currently does the computation is not overloaded, we get our 50 server/HC FPS.
Now for the disclaimer: this needs much more testing to make sure it doesn't introduce any other issues. I don't have any easy to reproduce benchmarks for the improvements, it's all based on my observations. My main question here is if you think something like this could work in the mission. The difficulties I see are: