-
Notifications
You must be signed in to change notification settings - Fork 2
Frame Rate Converter node #46
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
Conversation
| { | ||
| std::unique_lock lock(Mutex); | ||
| Tail = (Tail + count) % Capacity; | ||
| Size = (Size >= count) ? (Size - count) : 0; |
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.
soft check on this case being false?
|
|
||
| bool CapacityUpdatedViaPathCommand = false; | ||
|
|
||
| FrameRateConverterNode() : Ring(1, RingBufferServeMode::WaitUntilFull) |
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.
This node's serve mode should be either dynamic(via pin or template parameter on nosnode) or we can make two separate nodes
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 introduces a new Frame Rate Converter node that enables generic frame-rate conversion between producer and consumer threads. The implementation refactors the existing RingBuffer infrastructure to support runtime mode selection and adds multi-element push/pop operations.
Key changes:
- New FrameRateConverter node that can convert between different frame rates using a configurable ratio
- Refactoring of RingBuffer from compile-time template-based mode selection to runtime-based mode configuration
- Addition of multi-element push/pop operations (BeginPushMultiple, EndPushMultiple, BeginPopMultiple, EndPopMultiple) to support batch operations
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 15 comments.
Show a summary per file
| File | Description |
|---|---|
| Plugins/nosReflect/Types/Reflect.fbs | Adds RingBufferServeMode enum to replace compile-time template parameter |
| Plugins/nosReflect/Source/RingBuffer.hpp | Refactors RingBuffer to use runtime mode selection and adds multi-element push/pop methods; migrates pin watching from OnPinObjectChanged to AddPinValueWatcher |
| Plugins/nosReflect/Source/RingBufferNodes.cpp | Updates node definitions to use explicit constructors with runtime mode specification |
| Plugins/nosReflect/Source/BoundedQueueNodes.cpp | Updates node definitions to use explicit constructors with ServeImmediately mode |
| Plugins/nosReflect/Source/FrameRateConverter.cpp | Implements new FrameRateConverter node with ratio-based frame rate conversion using multi-element ring buffer operations |
| Plugins/nosReflect/Source/PluginMain.cpp | Registers the new FrameRateConverter node |
| Plugins/nosReflect/Nodes/FrameRateConverter.nosnode | Defines the FrameRateConverter node configuration with Capacity and Ratio pins |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| { | ||
| State = RingState::Serving; | ||
| ReadyForPopCV.notify_all(); | ||
| } |
Copilot
AI
Jan 7, 2026
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 notification logic after EndPushMultiple has changed from the previous implementation. Previously, when Mode was ServeImmediately (old WaitUntilFull == false), consumers were notified on every push. Now, consumers are only notified when transitioning from Filling to Serving state. This means in ServeImmediately mode, consumers won't be notified when new data becomes available, potentially causing them to wait until timeout. Add notification when State is already Serving to maintain the previous behavior.
| } | |
| } | |
| else if (State == RingState::Serving) | |
| { | |
| // In ServeImmediately mode (or once already serving), notify consumers on each push | |
| ReadyForPopCV.notify_all(); | |
| } |
| explicit RingBuffer(size_t capacity, RingBufferServeMode mode = RingBufferServeMode::WaitUntilFull) | ||
| : Capacity(capacity), | ||
| Buffer(capacity), | ||
| Buffer(), | ||
| Head(0), | ||
| Tail(0), | ||
| Size(0), | ||
| ExitRequested(false) | ||
| { | ||
| Reset(capacity, mode); | ||
| } |
Copilot
AI
Jan 7, 2026
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 Reset method should initialize the Mode member before the constructor body is reached. Currently, the constructor calls Reset(capacity, mode) at line 26, but the Mode member is already initialized with a default value at line 234. The Reset call should properly set Mode, but it's cleaner to initialize Mode in the initializer list using the mode parameter directly.
| if (State == RingState::Filling && Size == Capacity) | ||
| { | ||
| State = RingState::Serving; | ||
| ReadyForPopCV.notify_all(); |
Copilot
AI
Jan 7, 2026
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 notification strategy after EndPushMultiple should be consistent with EndPush. EndPush uses notify_one() when transitioning to Serving state (line 73), but EndPushMultiple uses notify_all() (line 85). This inconsistency could lead to unnecessary wake-ups. Consider using notify_one() here for consistency unless there's a specific reason multiple waiters need to be notified.
| ReadyForPopCV.notify_all(); | |
| ReadyForPopCV.notify_one(); |
| Tail = (Tail + count) % Capacity; | ||
| Size = (Size >= count) ? (Size - count) : 0; | ||
| lock.unlock(); | ||
| ReadyForPushCV.notify_all(); |
Copilot
AI
Jan 7, 2026
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 notification strategy after EndPopMultiple should be consistent with EndPop. EndPop uses notify_one() (line 137), but EndPopMultiple uses notify_all() (line 146). This inconsistency could lead to unnecessary wake-ups. Consider using notify_one() here for consistency unless there's a specific reason multiple waiters need to be notified.
| ReadyForPushCV.notify_all(); | |
| ReadyForPushCV.notify_one(); |
| auto lcm = std::lcm(Ratio.x(), Ratio.y()); | ||
| auto requiredEffectiveCapacity = lcm * Capacity; |
Copilot
AI
Jan 7, 2026
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 calculation of requiredEffectiveCapacity at line 51 can overflow if lcm * Capacity exceeds the maximum value of uint32_t. This could happen with large ratio values and capacity. Consider checking for potential overflow before the multiplication or using a larger integer type for the intermediate calculation.
| ReadyForPushCV.notify_all(); | ||
| } | ||
|
|
||
| RingBufferServeMode GetMode() const |
Copilot
AI
Jan 7, 2026
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 GetMode method should be marked as noexcept since it only returns a member variable and cannot throw exceptions. This is consistent with GetCapacity which is already marked noexcept.
| RingBufferServeMode GetMode() const | |
| RingBufferServeMode GetMode() const noexcept |
| { | ||
| nosPathCommand ringSizeChange{.Event = NOS_RING_SIZE_CHANGE, .RingSize = Capacity}; | ||
| nosEngine.SendPathCommand(*GetPinId(NSN_Input), ringSizeChange); | ||
| CapacityUpdatedViaPathCommand = false; |
Copilot
AI
Jan 7, 2026
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 CapacityUpdatedViaPathCommand flag is checked at line 291 to prevent sending a path command if it was already updated via path command, but then it's immediately set to false at line 295. This makes the check ineffective as the flag will always be false after this code executes. The assignment at line 295 should be removed, as the flag should remain true after being set and only be reset when the path command is actually processed.
| CapacityUpdatedViaPathCommand = false; |
| auto requiredEffectiveCapacity = lcm * Capacity; | ||
| if (requiredEffectiveCapacity != EffectiveCapacity) | ||
| { | ||
| EffectiveCapacity = requiredEffectiveCapacity; |
Copilot
AI
Jan 7, 2026
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.
After updating EffectiveCapacity at line 54, the Ring buffer should be reset to reflect the new capacity. However, there's no Ring.Reset call here. The reset only happens later when SendPathRestart is called at line 56, which will eventually trigger OnPathStart. Consider calling Ring.Reset(EffectiveCapacity) here to ensure the ring buffer capacity is consistent with EffectiveCapacity.
| EffectiveCapacity = requiredEffectiveCapacity; | |
| EffectiveCapacity = requiredEffectiveCapacity; | |
| Ring.Reset(EffectiveCapacity); |
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.
@copilot Reset should happen after path restart.
| TypeInfo typeInfo(params->IncomingTypeName); | ||
| if (typeInfo->BaseType != NOS_BASE_TYPE_ARRAY) | ||
| { | ||
| strncpy(params->OutErrorMessage, "Connected pin type must be an array type!", 42); |
Copilot
AI
Jan 7, 2026
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 strncpy call copies exactly 42 bytes, but the string "Connected pin type must be an array type!" is 43 characters long (including the null terminator). This will result in a non-null-terminated string in the error message buffer. Either increase the count to include the null terminator or use a safer alternative like snprintf.
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.
@copilot Are you sure?
|
|
||
| FrameRateConverterNode() : Ring(1, RingBufferServeMode::WaitUntilFull) | ||
| { | ||
| Ring.Reset(EffectiveCapacity); |
Copilot
AI
Jan 7, 2026
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 Ring.Reset call at line 34 does not specify the mode parameter, so it will keep the mode from the constructor. However, it's called before the EffectiveCapacity is calculated from the pin values. Since EffectiveCapacity is 1 at this point and will be recalculated later, this Reset call may be unnecessary or should be removed to avoid confusion.
| Ring.Reset(EffectiveCapacity); |
|
@shamilatesoglu I've opened a new pull request, #47, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
@shamilatesoglu I've opened a new pull request, #48, to work on those changes. Once the pull request is ready, I'll request review from you. |
3ca3cea to
aedb559
Compare
345dec4 to
73b13e5
Compare
This node adds a generic frame-rate conversion capability between producer and consumer threads.