Skip to content
This repository has been archived by the owner on Jun 4, 2024. It is now read-only.

Fix panels not receiving latest messages on newly subscribed topic that is already subscribed to by another panel #7113

Merged
merged 14 commits into from
Dec 19, 2023

Conversation

snosenzo
Copy link
Contributor

@snosenzo snosenzo commented Nov 13, 2023

User-Facing Changes

  • Fix panels not receiving latest messages on newly subscribed topic that is already subscribed to by another panel

Description
Supercedes: #7090

Because "subscriptions" haven't changed according to the underlying player it does not re-emit state. Which means that updatePlayerStateAction on the message pipeline store is never called to set the lastMessages on the newly subscribed topics.

This PR moves the logic for applying lastMessages to subscribed topics to updateSubscriberAction to emit these messages immediately instead of waiting for another underlying player emit state. I've removed the need to store newTopics across the actions because handling these topics happens entirely in the updateSubscriberAction. I've also added some logic to remove messages on topics that are no longer subscribed to from lastMessageByTopic so that stale messages are not passed when resubscribing to that topic before backfill resolves.

Some notes about reading the PR:
I've removed some useDelayedFixture since they were causing strange behavior with some stories. These shouldn't be necessary anymore since the MockMessagePipelineProvider should now handle "Player" lifecycle and doing backfill for initial subscriptions better.

I had to add a pauseFrame functionality that more closely mirrored player listener behavior to the MMPP so that we wait until all components finish rendering before updating the store.

Fixes: FG-5574


type Params<T> = {
/**
Copy link
Contributor Author

Choose a reason for hiding this comment

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

commented this because I had to debug the tests and wanted to have a better understanding of the parameters

Copy link
Contributor

Choose a reason for hiding this comment

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

Not your problem in this PR to fix - but since useMessageReducer is an internal API I do wonder if it would be a "simple" PR to remove the string[] variant. Then you can also drop the sidecar preloadType field.

@@ -358,11 +358,7 @@ describe("MessagePipelineProvider/useMessagePipeline", () => {
});
expect(result.current.subscriptions).toEqual([{ topic: "/input/foo" }]);

// Emit empty player state to process new subscriptions
Copy link
Contributor Author

Choose a reason for hiding this comment

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

no longer need to wait for a player to emit again to receive the last messages on a topic

expect(result.current).toEqual(2);

rerender({ topics: ["/bar", "/foo"] });

// no additional calls
Copy link
Contributor Author

Choose a reason for hiding this comment

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

no longer need player emit to push lastMessagesOnTOpic

@snosenzo snosenzo marked this pull request as ready for review December 11, 2023 21:40
activeData: {
currentTime: { sec: 0, nsec: 0 },
parameters: new Map([[urdfParamName, URDF3]]),
messages: [
Copy link
Member

Choose a reason for hiding this comment

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

was this just broken if these were getting ignored?

Copy link
Contributor

Choose a reason for hiding this comment

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

^ I too am curious - seems related to your Fixture code update about messages in activeData

Copy link
Contributor Author

@snosenzo snosenzo Dec 15, 2023

Choose a reason for hiding this comment

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

These weren't being ignored before my change, but they weren't going through the normal message pipeline. They were emitted on the initialization of the store, but because there are no subscribers, nothing would handle them. They would not be emitted again because subsequent dispatching to the MP would overwrite it. putting them in the frame lets them go through the normal messages mock message pipeline prop

@@ -112,13 +111,11 @@ export const UrdfDisplayMode: StoryObj = {
},
};

const fixture = useDelayedFixture({
Copy link
Member

Choose a reason for hiding this comment

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

did having this break something?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This wasn't breaking anything as of my final implementation, but I believe it was responsible for the failing story in a previous iteration. Since it doesn't do anything anymore I'll keep it as is


type Params<T> = {
/**
Copy link
Contributor

Choose a reason for hiding this comment

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

Not your problem in this PR to fix - but since useMessageReducer is an internal API I do wonder if it would be a "simple" PR to remove the string[] variant. Then you can also drop the sidecar preloadType field.

* @param state - Immutable. `undefined` when called on initialization or seek. Otherwise, the current state.
* @returns - New state. Must be new reference to trigger rerender.
*/
restore: (state: T | undefined) => T;
Copy link
Contributor

Choose a reason for hiding this comment

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

The comment says state is immutable - what makes that so? Does it need to be changed to Immutable<T> ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Was just trying to portray the original comment, will try to apply Immutable<T> but might complain in a bunch of places that I don't want to change in this PR

@@ -30,18 +30,37 @@ import {

const log = Log.getLogger(__filename);

type MessageReducer<T> = (arg0: T, message: MessageEvent) => T;
Copy link
Contributor

Choose a reason for hiding this comment

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

fun fact - arg0 comes to us from the earliest days of when I converted the codebase from flow to typescript :D

@@ -124,7 +124,7 @@ describe("PanelExtensionAdapter", () => {
await Promise.resolve();
});
expect(renderStates).toEqual([
{ currentFrame: [message], didSeek: false },
{ currentFrame: [], didSeek: false }, // first frame is empty because there are no subscribers yet
Copy link
Contributor

Choose a reason for hiding this comment

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

If there are no subscribers yet why did the previous version of the test have messages in currentFrame?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

because it would use the custom logic in MMPP that would add all of the existing messages to messagesBySubscriberId when update-subscriber was called. This now happens in a separate update-player action after update-subscribers was called.

@@ -80,7 +80,10 @@ export type Fixture = {
topics?: Topic[];
capabilities?: string[];
profile?: string;
activeData?: Partial<PlayerStateActiveData>;
/** Player active data does not include `messages`.
Copy link
Contributor

Choose a reason for hiding this comment

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

What does it mean that it does not include messages? Is it that any messages you provide in the activeData fixture are ignored?

activeData: {
currentTime: { sec: 0, nsec: 0 },
parameters: new Map([[urdfParamName, URDF3]]),
messages: [
Copy link
Contributor

Choose a reason for hiding this comment

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

^ I too am curious - seems related to your Fixture code update about messages in activeData

@@ -581,11 +572,6 @@ describe("MessagePipelineProvider/useMessagePipeline", () => {
});
expect(result.current.subscriptions).toEqual([{ topic: "/input/foo" }]);

// Emit empty player state to process new subscriptions
Copy link
Contributor

Choose a reason for hiding this comment

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

👏 Always happy to see some doubleAct be deleted :D

@@ -62,7 +62,8 @@ export type MessagePipelineInternalState = {
* for dispatching messages needs to iterate over the array of IDs.
*/
subscriberIdsByTopic: Map<string, string[]>;
newTopicsBySubscriberId: Map<string, Set<string>>;
/** This holds the last message emitted by the player on each topic. Attempt to use this before falling back to player backfill.
Copy link
Contributor

Choose a reason for hiding this comment

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

Remind me what it means to fall-back to player backfill in this context?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

player backfill reads the last message on all active subscriptions from the file and triggers a seek

@@ -227,36 +228,20 @@ function updateSubscriberAction(
action: UpdateSubscriberAction,
Copy link
Contributor

Choose a reason for hiding this comment

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

Github is terrible at letting me comment outside of changed lines but the comment a few lines above this that mentions newTopicsBySubscriberId is wrong now. You can probably write a better comment on what this function does given the new implementation.

}
}

const messagesBySubscriberId = new Map<string, MessageEvent[]>();
Copy link
Contributor

Choose a reason for hiding this comment

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

Is SubscriberId the same ID as newTopicsForId a few lines up? Both are the subscriber id? How come we have a new map of messages by subscriberId here but the action only has one id?

Copy link
Contributor

Choose a reason for hiding this comment

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

If my reading of this is correct - the downsteam impact of this change is that whenever a panel subscribes, all panels will get notified with empty frame data? There is no carry-over of the messagesBySubscriberId from the existing state. Is that a problem? No test seemed to think it was a problem but it does seem like there's some gap here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

responded in slack, but useMessageReducer and PanelExtensionAdapter are memoized by the message array within messagesBySubscriberId and will not read in a new frame unless that array has changed

@snosenzo snosenzo merged commit 537f45b into main Dec 19, 2023
15 checks passed
@snosenzo snosenzo deleted the message-pipeline-backfill branch December 19, 2023 22:16
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants