Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 18 additions & 7 deletions addons/recorder/fnc_captureLoop.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,11 @@ GVAR(PFHObject) = [
};
if (!_justInitialized && {!_isExcluded}) then {
private _unitRole = _x getVariable [QGVARMAIN(unitType), ""];
if (GVAR(captureFrameNo) % 10 == 0 || _unitRole == "") then {
private _weapons = [primaryWeapon _x, secondaryWeapon _x];

Choose a reason for hiding this comment

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

medium

The original condition GVAR(captureFrameNo) % 10 == 0 for recalculating _unitRole was based on a fixed frame interval. The new condition _weapons isNotEqualTo (_x getVariable [QGVAR(lastWeapons), []]) correctly triggers recalculation only when the unit's weapons change, which is a more accurate and efficient approach. This is a good optimization.

if (_weapons isNotEqualTo (_x getVariable [QGVAR(lastWeapons), []]) || _unitRole == "") then {
_unitRole = [_x] call FUNC(getUnitType);
_x setVariable [QGVARMAIN(unitType), _unitRole];
_x setVariable [QGVAR(lastWeapons), _weapons];
};

private _lifeState = 0;
Expand All @@ -147,6 +149,14 @@ GVAR(PFHObject) = [
_pos = getPosASL _x;
private _unitGroup = group _x;

private _scores = getPlayerScores _x;
private _scoresStr = _x getVariable [QGVAR(lastScoresStr), ""];
if (_scores isNotEqualTo (_x getVariable [QGVAR(lastScores), []])) then {
_scoresStr = _scores joinString ",";
_x setVariable [QGVAR(lastScores), _scores];
_x setVariable [QGVAR(lastScoresStr), _scoresStr];
};
Comment on lines +152 to +158

Choose a reason for hiding this comment

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

medium

Caching getPlayerScores _x and its string representation (_scoresStr) prevents redundant calls to getPlayerScores and joinString if the scores haven't changed. This is a good performance improvement, especially in a per-frame handler.


private _unitData = [
(_x getVariable QGVARMAIN(id)), //1
_pos, //2
Expand All @@ -156,20 +166,21 @@ GVAR(PFHObject) = [
if (alive _x) then {name _x} else {""}, //6
BOOL(isPlayer _x), //7
_unitRole, //8
GVAR(captureFrameNo), // frame 9
0, // frame placeholder for comparison (set before sending) 9
if (!isNil "ace_medical_status_fnc_hasStableVitals") then {BOOL([_x] call ace_medical_status_fnc_hasStableVitals)} else {true}, // 10
if (!isNil "ace_medical_status_fnc_isBeingDragged") then {BOOL([_x] call ace_medical_status_fnc_isBeingDragged)} else {false}, // 11
(getPlayerScores _x) joinString ",", // scores 12
_scoresStr, // scores 12
_x call CBA_fnc_vehicleRole, // vehicle role 13
if (!isNull objectParent _x) then {(objectParent _x) getVariable [QGVARMAIN(id), -1]} else {-1}, // 14
stance _x, // 15
groupID _unitGroup, // 16 group name (dynamic)
str side _unitGroup // 17 side (dynamic)
];

if (_x getVariable ["unitData", []] isNotEqualTo _unitData) then {
if (_x getVariable [QGVARMAIN(unitData), []] isNotEqualTo _unitData) then {
_x setVariable [QGVARMAIN(unitData), +_unitData];
_unitData set [8, GVAR(captureFrameNo)];
[":SOLDIER:STATE:", _unitData] call EFUNC(extension,sendData);
_x setVariable [QGVARMAIN(unitData), _unitData];
};
Comment on lines +180 to 184

Choose a reason for hiding this comment

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

high

The fix for unitData state deduplication is critical. By comparing against QGVARMAIN(unitData) and correctly setting the captureFrameNo before sending, redundant extension calls for dead/stationary units are eliminated. This directly addresses a high-impact performance issue.

};
false
Expand All @@ -182,7 +193,7 @@ GVAR(PFHObject) = [
_class = _vehType call FUNC(getClass);
private _vic = _x;
private _toExcludeKind = false;
private _kindList = parseSimpleArray EGVAR(settings,excludeKindFromRecord);
private _kindList = GVAR(excludeKindList);

Choose a reason for hiding this comment

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

medium

Replacing parseSimpleArray EGVAR(settings,excludeKindFromRecord) with the cached GVAR(excludeKindList) significantly improves performance by avoiding repeated parsing of the exclusion list within the per-frame handler. This aligns with the general rule of caching expensive operations in performance-critical code.

References
  1. In performance-critical code such as per-frame handlers, cache the results of expensive operations (e.g., parseSimpleArray) in local variables if the result is used multiple times, such as for both an emptiness check and a subsequent loop.

if (_kindList isNotEqualTo []) then {
{
if (_vic isKindOf _x) exitWith {
Expand All @@ -191,7 +202,7 @@ GVAR(PFHObject) = [
} forEach _kindList;
};
private _toExcludeClass = false;
private _classList = parseSimpleArray EGVAR(settings,excludeClassFromRecord);
private _classList = GVAR(excludeClassList);

Choose a reason for hiding this comment

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

medium

Similar to the excludeKindList, caching EGVAR(settings,excludeClassFromRecord) into GVAR(excludeClassList) and using the cached variable here avoids repeated parsing, leading to better performance in this per-frame handler.

References
  1. In performance-critical code such as per-frame handlers, cache the results of expensive operations (e.g., parseSimpleArray) in local variables if the result is used multiple times, such as for both an emptiness check and a subsequent loop.

if (_classList isNotEqualTo []) then {
{
if (typeOf _vic == _x) exitWith {
Expand Down
54 changes: 26 additions & 28 deletions addons/recorder/fnc_handleMarkers.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,12 @@ EGVAR(listener,markers) = [QGVARMAIN(handleMarker), {

// handle created markers
{
GVAR(excludeMarkerList) = if (!isNil QEGVAR(settings,excludeMarkerFromRecord)) then {
parseSimpleArray EGVAR(settings,excludeMarkerFromRecord)
} else {
[]
};
Comment on lines +164 to +168

Choose a reason for hiding this comment

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

medium

Caching the excludeMarkerList at initialization (fnc_init.sqf) and then using the global variable GVAR(excludeMarkerList) here avoids repeated calls to parseSimpleArray within the event handler. This is a good performance optimization for frequently executed code.

References
  1. In performance-critical code such as per-frame handlers, cache the results of expensive operations (e.g., parseSimpleArray) in local variables if the result is used multiple times, such as for both an emptiness check and a subsequent loop.


/*
Event Handler: MarkerCreated
Description:
Expand All @@ -181,13 +187,11 @@ EGVAR(listener,markers) = [QGVARMAIN(handleMarker), {
// check for excluded values in marker name. if name contains at least one value, skip sending traffic to server
// if value is undefined, then skip
private _isExcluded = false;
if (!isNil QEGVAR(settings,excludeMarkerFromRecord)) then {
{
if ((str _marker) find _x >= 0) exitWith {
_isExcluded = true;
};
} forEach (parseSimpleArray EGVAR(settings,excludeMarkerFromRecord));
};
{
if ((str _marker) find _x >= 0) exitWith {
_isExcluded = true;
};
} forEach GVAR(excludeMarkerList);
Comment on lines +190 to +194

Choose a reason for hiding this comment

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

medium

By using the pre-cached GVAR(excludeMarkerList), the expensive parseSimpleArray call is removed from this frequently executed loop, leading to improved performance.

References
  1. In performance-critical code such as per-frame handlers, cache the results of expensive operations (e.g., parseSimpleArray) in local variables if the result is used multiple times, such as for both an emptiness check and a subsequent loop.

if (_isExcluded) exitWith {};

private _event = _this;
Expand Down Expand Up @@ -234,13 +238,11 @@ EGVAR(listener,markers) = [QGVARMAIN(handleMarker), {
// check for excluded values in marker name. if name contains at least one value, skip sending traffic to server
// if value is undefined, then skip
private _isExcluded = false;
if (!isNil QEGVAR(settings,excludeMarkerFromRecord)) then {
{
if ((str _marker) find _x >= 0) exitWith {
_isExcluded = true;
};
} forEach (parseSimpleArray EGVAR(settings,excludeMarkerFromRecord));
};
{
if ((str _marker) find _x >= 0) exitWith {
_isExcluded = true;
};
} forEach GVAR(excludeMarkerList);
Comment on lines +241 to +245

Choose a reason for hiding this comment

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

medium

Similar to the previous comment, using the cached GVAR(excludeMarkerList) here optimizes the marker update handler by avoiding redundant parsing.

References
  1. In performance-critical code such as per-frame handlers, cache the results of expensive operations (e.g., parseSimpleArray) in local variables if the result is used multiple times, such as for both an emptiness check and a subsequent loop.

if (_isExcluded) exitWith {};

private _pos = ATLToASL (markerPos [_marker, true]);
Expand Down Expand Up @@ -269,13 +271,11 @@ EGVAR(listener,markers) = [QGVARMAIN(handleMarker), {
// check for excluded values in marker name. if name contains at least one value, skip sending traffic to server
// if value is undefined, then skip
private _isExcluded = false;
if (!isNil QEGVAR(settings,excludeMarkerFromRecord)) then {
{
if ((str _marker) find _x > -1) exitWith {
_isExcluded = true;
};
} forEach (parseSimpleArray EGVAR(settings,excludeMarkerFromRecord));
};
{
if ((str _marker) find _x > -1) exitWith {
_isExcluded = true;
};
} forEach GVAR(excludeMarkerList);
Comment on lines +274 to +278

Choose a reason for hiding this comment

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

medium

The change to use GVAR(excludeMarkerList) here is consistent with the other marker handlers and provides the same performance benefits by avoiding repeated parseSimpleArray calls.

References
  1. In performance-critical code such as per-frame handlers, cache the results of expensive operations (e.g., parseSimpleArray) in local variables if the result is used multiple times, such as for both an emptiness check and a subsequent loop.

if (_isExcluded) exitWith {};

[QGVARMAIN(handleMarker), ["DELETED", _marker, player]] call CBA_fnc_serverEvent;
Expand All @@ -294,13 +294,11 @@ EGVAR(listener,markers) = [QGVARMAIN(handleMarker), {
// check for excluded values in marker name. if name contains at least one value, skip sending traffic to server
// if value is undefined, then skip
private _isExcluded = false;
if (!isNil QEGVAR(settings,excludeMarkerFromRecord)) then {
{
if ((_marker) find _x >= 0) exitWith {
_isExcluded = true;
};
} forEach (parseSimpleArray EGVAR(settings,excludeMarkerFromRecord));
};
{
if ((_marker) find _x >= 0) exitWith {
_isExcluded = true;
};
} forEach GVAR(excludeMarkerList);
Comment on lines +297 to +301

Choose a reason for hiding this comment

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

medium

This change ensures that the initial marker collection also benefits from the cached exclusion list, preventing unnecessary parsing during mission initialization.

References
  1. In performance-critical code such as per-frame handlers, cache the results of expensive operations (e.g., parseSimpleArray) in local variables if the result is used multiple times, such as for both an emptiness check and a subsequent loop.

if (_isExcluded) then {continue};


Expand Down
9 changes: 9 additions & 0 deletions addons/recorder/fnc_init.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,15 @@ GVAR(autoStart) = EGVAR(settings,autoStart);
*/
GVAR(minMissionTime) = EGVAR(settings,minMissionTime);

GVAR(excludeKindList) = parseSimpleArray EGVAR(settings,excludeKindFromRecord);
GVAR(excludeClassList) = parseSimpleArray EGVAR(settings,excludeClassFromRecord);

GVAR(excludeMarkerList) = if (!isNil QEGVAR(settings,excludeMarkerFromRecord)) then {
parseSimpleArray EGVAR(settings,excludeMarkerFromRecord)
} else {
[]
};
Comment on lines +82 to +89

Choose a reason for hiding this comment

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

high

Caching the exclusion lists (excludeKindList, excludeClassList, excludeMarkerList) at initialization is a significant performance improvement. This prevents repeated parsing of these settings in performance-critical loops, such as the captureLoop and marker handlers. This aligns with the general rule of caching expensive operations.

References
  1. In performance-critical code such as per-frame handlers, cache the results of expensive operations (e.g., parseSimpleArray) in local variables if the result is used multiple times, such as for both an emptiness check and a subsequent loop.


/*
VARIABLE: OCAP_version
Global variable that represents the version of OCAP addon being used [String]
Expand Down
10 changes: 3 additions & 7 deletions addons/recorder/fnc_isKindOfApc.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,6 @@ Author:
---------------------------------------------------------------------------- */
#include "script_component.hpp"

_bool = false;
{
if (_this isKindOf _x) exitWith {_bool = true;};
false;
} count ["Wheeled_APC_F","Tracked_APC","APC_Wheeled_01_base_F","APC_Wheeled_02_base_F",
"APC_Wheeled_03_base_F","APC_Tracked_01_base_F","APC_Tracked_02_base_F","APC_Tracked_03_base_F"];
_bool
["Wheeled_APC_F","Tracked_APC","APC_Wheeled_01_base_F","APC_Wheeled_02_base_F",
"APC_Wheeled_03_base_F","APC_Tracked_01_base_F","APC_Tracked_02_base_F","APC_Tracked_03_base_F"]
findIf {_this isKindOf _x} != -1
Comment on lines +26 to +28

Choose a reason for hiding this comment

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

medium

Replacing the count anti-pattern with findIf for early-exit in APC classification is a good optimization. count iterates through the entire array, while findIf stops as soon as a match is found, which is more efficient for this use case.

References
  1. In performance-critical code such as per-frame handlers, cache the results of expensive operations (e.g., parseSimpleArray) in local variables if the result is used multiple times, such as for both an emptiness check and a subsequent loop.

41 changes: 24 additions & 17 deletions addons/recorder/fnc_telemetryLoop.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -41,25 +41,32 @@ Author:
private _sideData = [];
{
private _s = _x;
private _sUnits = _allUnits select {side _x isEqualTo _s};
private _sDead = _allDeadMen select {side _x isEqualTo _s};
private _sGroups = _allGroups select {side _x isEqualTo _s};
private _sVeh = _vehicles select {side _x isEqualTo _s};

private _localUnits = _sUnits select {local _x};
private _remoteUnits = _sUnits select {!local _x};
private _localDead = _sDead select {local _x};
private _remoteDead = _sDead select {!local _x};
private _localGroups = _sGroups select {local _x};
private _remoteGroups = _sGroups select {!local _x};
private _localVeh = _sVeh select {local _x && !(_x isKindOf "WeaponHolderSimulated")};
private _remoteVeh = _sVeh select {!local _x && !(_x isKindOf "WeaponHolderSimulated")};
private _localWH = _sVeh select {local _x && _x isKindOf "WeaponHolderSimulated"};
private _remoteWH = _sVeh select {!local _x && _x isKindOf "WeaponHolderSimulated"};
private _localUnits = 0; private _localAlive = 0; private _remoteUnits = 0; private _remoteAlive = 0;
{ if (side _x isEqualTo _s) then {
if (local _x) then { _localUnits = _localUnits + 1; if (alive _x) then { _localAlive = _localAlive + 1 } }
else { _remoteUnits = _remoteUnits + 1; if (alive _x) then { _remoteAlive = _remoteAlive + 1 } };
}} forEach _allUnits;

private _localDead = 0; private _remoteDead = 0;
{ if (side _x isEqualTo _s) then {
if (local _x) then { _localDead = _localDead + 1 } else { _remoteDead = _remoteDead + 1 };
}} forEach _allDeadMen;

private _localGroups = 0; private _remoteGroups = 0;
{ if (side _x isEqualTo _s) then {
if (local _x) then { _localGroups = _localGroups + 1 } else { _remoteGroups = _remoteGroups + 1 };
}} forEach _allGroups;

private _localVeh = 0; private _remoteVeh = 0; private _localWH = 0; private _remoteWH = 0;
{ if (side _x isEqualTo _s) then {
private _isWH = _x isKindOf "WeaponHolderSimulated";
if (local _x) then { if (_isWH) then { _localWH = _localWH + 1 } else { _localVeh = _localVeh + 1 } }
else { if (_isWH) then { _remoteWH = _remoteWH + 1 } else { _remoteVeh = _remoteVeh + 1 } };
}} forEach _vehicles;

_sideData pushBack [
[count _localUnits, {alive _x} count _localUnits, count _localDead, count _localGroups, count _localVeh, count _localWH],
[count _remoteUnits, {alive _x} count _remoteUnits, count _remoteDead, count _remoteGroups, count _remoteVeh, count _remoteWH]
[_localUnits, _localAlive, _localDead, _localGroups, _localVeh, _localWH],
[_remoteUnits, _remoteAlive, _remoteDead, _remoteGroups, _remoteVeh, _remoteWH]
];
Comment on lines +44 to 70

Choose a reason for hiding this comment

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

high

The refactoring of the telemetry loop to use single-pass counting instead of chained select calls is a significant performance improvement. The original code iterated over _allUnits, _allDeadMen, _allGroups, and _vehicles multiple times for each side. The new approach iterates once per collection and categorizes/counts within that single pass, drastically reducing redundant iterations. This directly addresses the general rule about avoiding multiple iterations for mutually exclusive categories.

References
  1. To optimize performance when counting items in an array that fall into two mutually exclusive categories, count one category directly and derive the count of the second by subtracting from the total. Avoid iterating over the array twice.

} forEach [east, west, independent, civilian];

Expand Down