-
-
Notifications
You must be signed in to change notification settings - Fork 21
Optimize RPN execution to reduce FPS overhead via deduplication #33
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
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
There was a problem hiding this 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
ReadRPNCodestructure to map single RPN code executions to multiple SimVar/StringSimVar subscribers - Removed
Namefield fromSimVarandStringSimVarstructs, addingclientpointer instead for tracking ownership - Moved rolling read index from per-client to global scope to track position in deduplicated RPN code list
- Refactored
ReadSimVarfunctions 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 |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
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).
| // clear RNP code list | |
| // clear RPN code list |
| if ((simVar.Value > floatVal) && (simVar.Value - floatVal < 0.00001F)) { | ||
| continue; | ||
| } else if ((simVar.Value < floatVal) && (floatVal - simVar.Value < 0.00001F)) { | ||
| continue; | ||
| } |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
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.
| 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(); |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
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.
| //client->RollingDataReadIndex = client->SimVars.begin(); |
| 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); | ||
| } |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
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.
| 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; | ||
| } |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
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.
| uint16_t RollingClientDataReadIndex; | ||
| }; | ||
|
|
||
| // Runtime Rolling CLient Data reading Index |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
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".
| // Runtime Rolling CLient Data reading Index | |
| // Runtime Rolling Client Data reading Index |
| ++sit; | ||
| } | ||
| } | ||
| //remove empty RNP code |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
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).
| //remove empty RNP code | |
| //remove empty RPN code |
| } else { | ||
| ReadRPNCode rpnCode; | ||
| rpnCode.Code = code; | ||
| rpnCode.RetType = 0;//hardcoded type id |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
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.
| } else { | ||
| ReadRPNCode rpnCode; | ||
| rpnCode.Code = code; | ||
| rpnCode.RetType = 2;//hardcoded type id |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
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.
| 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); | ||
| } |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
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.
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.
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.