Skip to content

Conversation

@cpuwolf
Copy link
Contributor

@cpuwolf cpuwolf commented Nov 24, 2025

Description:

This PR introduces a performance enhancement for handling multiple clients subscribing to identical SimVars. Currently, if multiple clients add the same RPN code (e.g., via MF.SimVars.Add), the WASM module executes the code redundantly for every instance within a single frame.

Changes:

Implemented a "calculate once, distribute to many" logic. The WASM module will now detect duplicate RPN codes and execute them only once per frame.
Introduced a new ReadRPNCode structure to manage the mapping of single code executions to multiple SimVars/StringSimVars.

//RPN code execution for reading values in every frame
struct ReadRPNCode {
	std::string Code;
	int RetType; //RetType: 0:float 1:integer 2:string
	std::vector<SimVar> SimVars;
	std::vector<StringSimVar> StringSimVars;
};

‎wasm ‎1

Stability:

This patch is highly stable and production-ready. It has been deployed to over 1,000 customers for more than a year with no reported issues.

While multiple clients Add(MF.SimVars.Add.xxxxRPNcodexxx) the exact same SimVars to WASM,
From MSFS2020 perspective, it is unnecessary to execute the same RPNcode several times in each frame. This is an enhancement change to reduce FPS impact
WASM will smartly execute duplicated SimVars only once in each frame.

introduce new data structure:

//RPN code execution for reading values in every frame
struct ReadRPNCode {
	std::string Code;
	int RetType; //RetType: 0:float 1:integer 2:string
	std::vector<SimVar> SimVars;
	std::vector<StringSimVar> StringSimVars;
};

--
This patch is stable because we have 1000+ customers who have been using these changes over a year
@cpuwolf cpuwolf changed the title Avoid duplicated reading RPN code execution across multiple clients Optimize RPN execution to reduce FPS overhead via deduplication Nov 24, 2025
@DocMoebiuz DocMoebiuz requested a review from Copilot December 4, 2025 10:08
Copilot finished reviewing on behalf of DocMoebiuz December 4, 2025 10:12
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR optimizes RPN (Reverse Polish Notation) code execution in the WASM module by implementing a deduplication mechanism. When multiple clients subscribe to identical SimVars with the same RPN code, the module now executes each unique code only once per frame and distributes the result to all subscribers, reducing computational overhead.

Key Changes

  • Introduced ReadRPNCode structure to map single RPN code executions to multiple SimVar/StringSimVar subscribers
  • Removed Name field from SimVar and StringSimVar structs, adding client pointer instead for tracking ownership
  • Moved rolling read index from per-client to global scope to track position in deduplicated RPN code list
  • Refactored ReadSimVar functions to execute RPN code once and update all associated SimVars

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

WriteSimVar(simVar, client);
}
client->StringSimVars.clear();
// clear RNP code list
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

Typo in comment: "RNP" should be "RPN" to match the actual data structure name (ReadRPNCode).

Suggested change
// clear RNP code list
// clear RPN code list

Copilot uses AI. Check for mistakes.
Comment on lines +479 to +483
if ((simVar.Value > floatVal) && (simVar.Value - floatVal < 0.00001F)) {
continue;
} else if ((simVar.Value < floatVal) && (floatVal - simVar.Value < 0.00001F)) {
continue;
}
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

The floating-point comparison logic is flawed. The condition (simVar.Value > floatVal) && (simVar.Value - floatVal < 0.00001F) will skip updates when values are nearly equal AND the old value is greater. However, if simVar.Value is exactly equal to floatVal, or if they differ by more than 0.00001, the update proceeds. The logic should use std::abs(simVar.Value - floatVal) < 0.00001F to properly check if values are approximately equal regardless of which is larger. Also note the epsilon threshold may be too small for some float ranges.

Copilot uses AI. Check for mistakes.
std::cout << "MobiFlight[" << client->Name.c_str() << "]: Cleared SimVar tracking." << std::endl;
//client->RollingClientDataReadIndex = client->SimVars.begin();
client->RollingClientDataReadIndex = 0;
//client->RollingDataReadIndex = client->SimVars.begin();
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

Outdated comment reference. The comment refers to client->RollingDataReadIndex but the code now uses the global RollingDataReadIndex. Either update the comment to reflect the current implementation or remove it as it may cause confusion.

Suggested change
//client->RollingDataReadIndex = client->SimVars.begin();

Copilot uses AI. Check for mistakes.
Comment on lines 323 to +334
SimVars->push_back(newSimVar);

//duplicated SimVar
if (pdupRpn) {
pdupRpn->SimVars.push_back(newSimVar);
} else {
ReadRPNCode rpnCode;
rpnCode.Code = code;
rpnCode.RetType = 0;//hardcoded type id
rpnCode.SimVars.push_back(newSimVar);
RPNCodelist.push_back(rpnCode);
}
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

Data synchronization issue: newSimVar is pushed to both client->SimVars (line 323) and rpn.SimVars (line 327 or 332). These are separate copies of the struct. When ReadSimVarFloat updates simVar.Value in rpn.SimVars (line 484), the corresponding entry in client->SimVars becomes stale and won't reflect the current value. While SimConnect receives updates via WriteSimVar, the client->SimVars vector will contain outdated values, which could cause issues if this vector is read elsewhere in the codebase or during debugging.

Copilot uses AI. Check for mistakes.
Comment on lines +529 to 535
for (int i=0; i < maxVarsPerFrame; ++i) {
ReadSimVar(RPNCodelist.at(RollingDataReadIndex));

for (int i=0; i < maxVarsPerFrame; ++i) {
if(client->RollingClientDataReadIndex < SimVars->size() ) {
ReadSimVar(SimVars->at(client->RollingClientDataReadIndex), client);
}
else {
ReadSimVar(StringSimVars->at(client->RollingClientDataReadIndex - SimVars->size()), client);
}
client->RollingClientDataReadIndex++;
if (client->RollingClientDataReadIndex >= totalSimVars)
client->RollingClientDataReadIndex = 0;
}
RollingDataReadIndex++;
if (RollingDataReadIndex >= totalSimVars)
RollingDataReadIndex = 0;
}
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

Potential out-of-bounds access. If RPNCodelist is empty (totalSimVars == 0), maxVarsPerFrame will be 0, and the loop won't execute. However, if the list becomes empty during execution (e.g., all clients disconnect between frames), and RollingDataReadIndex is non-zero from a previous frame, the next frame could attempt to access RPNCodelist.at(RollingDataReadIndex) with an invalid index. Add a check: if (totalSimVars == 0) { RollingDataReadIndex = 0; return; } before the loop.

Copilot uses AI. Check for mistakes.
uint16_t RollingClientDataReadIndex;
};

// Runtime Rolling CLient Data reading Index
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

Typo in comment: "CLient" should be "Client".

Suggested change
// Runtime Rolling CLient Data reading Index
// Runtime Rolling Client Data reading Index

Copilot uses AI. Check for mistakes.
++sit;
}
}
//remove empty RNP code
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

Typo in comment: "RNP" should be "RPN" to match the actual data structure name (ReadRPNCode).

Suggested change
//remove empty RNP code
//remove empty RPN code

Copilot uses AI. Check for mistakes.
} else {
ReadRPNCode rpnCode;
rpnCode.Code = code;
rpnCode.RetType = 0;//hardcoded type id
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

Hardcoded RetType value (0 for float) reduces maintainability. Consider defining an enum or constants like RPN_TYPE_FLOAT = 0, RPN_TYPE_INTEGER = 1, RPN_TYPE_STRING = 2 to make the code more readable and maintainable.

Copilot uses AI. Check for mistakes.
} else {
ReadRPNCode rpnCode;
rpnCode.Code = code;
rpnCode.RetType = 2;//hardcoded type id
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

Hardcoded RetType value (2 for string) reduces maintainability. Consider defining an enum or constants like RPN_TYPE_FLOAT = 0, RPN_TYPE_INTEGER = 1, RPN_TYPE_STRING = 2 to make the code more readable and maintainable.

Copilot uses AI. Check for mistakes.
Comment on lines 380 to +391
StringSimVars->push_back(newStringSimVar);

//duplicated SimVar
if (pdupRpn) {
pdupRpn->StringSimVars.push_back(newStringSimVar);
} else {
ReadRPNCode rpnCode;
rpnCode.Code = code;
rpnCode.RetType = 2;//hardcoded type id
rpnCode.StringSimVars.push_back(newStringSimVar);
RPNCodelist.push_back(rpnCode);
}
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

Data synchronization issue: newStringSimVar is pushed to both client->StringSimVars (line 380) and rpn.StringSimVars (line 384 or 389). These are separate copies of the struct. When ReadSimVarString updates simVar.Value in rpn.StringSimVars (line 504), the corresponding entry in client->StringSimVars becomes stale and won't reflect the current value. While SimConnect receives updates via WriteSimVar, the client->StringSimVars vector will contain outdated values, which could cause issues if this vector is read elsewhere in the codebase or during debugging.

Copilot uses AI. Check for mistakes.
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