Skip to content
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

Fix/color change bug #346

Merged
merged 13 commits into from
Nov 28, 2023
Merged

Fix/color change bug #346

merged 13 commits into from
Nov 28, 2023

Conversation

meganrm
Copy link
Contributor

@meganrm meganrm commented Nov 22, 2023

Problem

in working on the color picker @interim17 and @ShrimpCryptid noticed a bug with wrong color being applied

Solution

The fix was to check if the color changes coming into the app had been reset to defaults. But in debugging we found that the color handling was confusing. There are two different color arrays: an array where each item is a single color, and colorsData where each color is represented by 4 numbers, so it's 4 times as long as the actual number of colors. We identify colors by their index in the first array, but sometimes need to switch between the two different indexes, and in each case they were labeled something like colorIndex or just index. Here I renamed everything as either a colorId :index into the shorter array where each item is a color and colorDataIndex: index into the 4 timex longer array. Hopefully this will reduce future confusion.

So the majority of this changeset is documentation and tests.

with @interim17

Type of change

Please delete options that are not relevant.

  • Bug fix (non-breaking change which fixes an issue)
  • This change requires a documentation update
  • This change requires updated or new tests

Steps to Verify:

  1. npm start
  2. select a model
  3. change colors, add new colors, etc, should be stable.

@meganrm meganrm requested a review from a team as a code owner November 22, 2023 23:19
@meganrm meganrm requested review from toloudis, frasercl and ascibisz and removed request for a team November 22, 2023 23:19
) &&
!isEqual(
selectionStateInfo.colorChanges,
defaultProps.selectionStateInfo.colorChanges
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 was the change that fixed the bug

Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if this function would be more performant if we updated to newer react and use useEffect. I always wonder if the number of checks here could bog things down.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it would be clearer to read, but I imagine at some level hooks are doing all these checks anyway to know if they should run. My understanding is class components are equally performant to function/hook based components in a one to one comparison. you can get increases in performance if you manage to make your overall tree structure less nested which is one of the benefits

Copy link

github-actions bot commented Nov 22, 2023

jest coverage report 🧪

Total coverage

Status Category Percentage Covered / Total
🔴 Statements 45.86% (+5.72% 🔼) 2251/4908
🔴 Branches 44.75% 840/1877
🔴 Functions 43.48% (+6.26% 🔼) 474/1090
🔴 Lines 45.85% (+5.46% 🔼) 2143/4673

Status of coverage: 🟢 - ok, 🟡 - slightly more than threshold, 🔴 - under the threshold

Show files with reduced coverage 🔻

Reduced coverage

Status Filename Statements Branches Functions Lines
🔴 src/viewport 14.76% 12.12% 15.38% 14.28%
🔴 index.tsx 14.76% 12.12% 15.38% 14.28%

Status of coverage: 🟢 - ok, 🟡 - slightly more than threshold, 🔴 - under the threshold

const colorArray = this.colorsData;
const colorToCheck = map(color, (num) => round(num, 6));
for (let i = 0; i < colorArray.length - 3; i += 4) {
const index = i / 4;
const index = i;
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 was also a bug

if (!this.visGeomMap.has(id)) {
this.visGeomMap.set(id, DEFAULT_MESH_NAME);
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

these are just moved up, I grouped all the private methods for handling color together, followed by all the public methods

this.setColorForIds(agentIds, colorId);
this.updateScene(this.currentSceneAgents);
}

public getColorForIndex(index: number): Color {
public getColorForColorId(colorId: number): Color {
if (colorId < 0) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think these cases will be impossible to hit. I wrote them as I was thinking of edge cases writing tests.

* Agent color handling
* @property this.colorsData is an array of floats, each 4 floats is a color
* @param dataColorIndex is an index into the colorsData array, so it is a multiple of 4
* @param colorId is an index into the numberOfColors, so it is a number between 0
Copy link
Contributor

Choose a reason for hiding this comment

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

what is the context of these @params ? this comment seems like it's not attached to anything. Also it might be a nice improvement to shift all of the color functions out to their own module.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah this comment is for the whole section of "agent color handling". I do agree it should be its own module (in addition to a lot of other things in visGeometry). I was consistent in all the following functions when using these two different names for parameters instead of generic things like "index" or "colorIndex" that we had before. I thought it would be clearer here than having the same comments for every function that used one of these

Copy link
Contributor

Choose a reason for hiding this comment

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

It's the @param tags that are not helping. They look like jsdoc tags for autogenerated documentation and you can't tell what they are parameters for. I think it would be clearer to have each function get its own jsdoc if you want to leave the code here in the middle of this module.

src/visGeometry/index.ts Outdated Show resolved Hide resolved
}

public applyColorToAgents(agentIds: number[], colorId: number): void {
/**
* Sets one color for a set of ids
*/
Copy link
Contributor

Choose a reason for hiding this comment

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

jsdoc style comments should go above the function name and not below it.

* Agent color handling: private methods
*/

private get numberOfColors(): number {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: I feel this would be more readable when it is used later in the code, if it were a simple function. It always messes me up when I think it's a simple variable and then wonder, who is updating its value when we add a new color??

Comment on lines +1 to +6
/* eslint-disable @typescript-eslint/no-explicit-any */
/* NOTE: this file is testing many private
methods and properties of VisGeometry.ts
which we do by using (visGeometry as any)
so we need to disable the eslint rule here
*/
Copy link
Contributor

@ShrimpCryptid ShrimpCryptid Nov 28, 2023

Choose a reason for hiding this comment

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

I didn't know you could do this! Is this the preferred way (in our team) to do testing of private members?

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 was just a stack overflow suggestion, I'm not sure if we have a preferred way of testing private methods.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't know if we have standardized on a way to do this across many projects. IMO this is very acceptable but a tradeoff:
(1) It's fair game to do this to simply unit test functions that are supposed to be private/internal. It lets you get at those functions without having to modify the actual code. Idk if typescript has other clean ways to do it (that would be more lintable).
(2) if you are trying to test the public api then this could just confuse things about what is a class's intended contract to the outside world

Copy link
Contributor Author

Choose a reason for hiding this comment

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

regarding #2: should I separate out a file that is only the private methods? I think there is only one public method I test here

Copy link
Contributor

Choose a reason for hiding this comment

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

If you feel like it. I am not worried about it for this code.

Comment on lines +1193 to +1206
const colorDataIndex = this.getColorDataIndex(newColor);
if (colorDataIndex !== -1) {
// found the color, need to return the colorId to the
// external caller, with no other changes needed
return this.convertDataColorIndexToId(colorDataIndex);
}

const newIndex = this.colorsData.length;
// the color isn't in colorsData, so add it and return the colorId
const newColorDataIndex = this.colorsData.length;
const newArray = [...this.colorsData, ...newColor];
const newColorData = new Float32Array(newArray.length);
newColorData.set(newArray);
this.colorsData = newColorData;
this.renderer.updateColors(this.colorsData.length / 4, this.colorsData);
return newIndex;
this.renderer.updateColors(this.numberOfColors, this.colorsData);
return this.convertDataColorIndexToId(newColorDataIndex);
Copy link
Contributor

@ShrimpCryptid ShrimpCryptid Nov 28, 2023

Choose a reason for hiding this comment

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

Are we worried about performance issues here?

Like, if I keep setting different colors for agents and then changing them, all of the intermediate colors would get added to this.colorsData. I don't think the array itself would cause issues (since it would take up like, 67 MB max if ignoring alpha values), but searching through it to find color indices later might be slower since it could potentially have up to 16 million entries.

Edit: I take back the 67 MB max figure actually, since it looks like colors components are stored as floats and not as 1-byte integers. So, all combinations of RGB colors, each stored as a 4-byte float in arrays of length four, would be...

256^3 colors * 4 floats/color * 4 bytes/float = 134,217,728 bytes = 134.2 MB

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah it's a good question. I don't think there is any real issue with just adding a new color every time we change color even if it already exists. My thinking was around keeping the data consistent ie, what if we wanted to select all agents that have the same color? But we don't currently have a use case for that. I could go either way on this

Copy link
Contributor

@toloudis toloudis Nov 28, 2023

Choose a reason for hiding this comment

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

I would expect the array of color data that gets passed to the shader, to be the same length as the number of agent types...keeping that small and efficient is good for the shader that uses it. But it will only be "bad" in the case Peyton describes where people start putting a lot of unused colors in the array and it gets to like more than 2-3x the number of agent types and we get extra cache misses on lookups and just have dead data lying around for no reason.

Comment on lines 1248 to +1251
return new Color(
this.colorsData[index * 4],
this.colorsData[index * 4 + 1],
this.colorsData[index * 4 + 2]
this.colorsData[colorId * 4],
this.colorsData[colorId * 4 + 1],
this.colorsData[colorId * 4 + 2]
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this return the alpha channel, since we're already storing it anyways?

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 Color from THREE js doesn't accept 4th term. but it is a question for when we're actually supporting transparency

Copy link
Contributor

@toloudis toloudis left a comment

Choose a reason for hiding this comment

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

The code changes look good.

There are still a bunch of jsdoc function comments in here which are below the function declaration, which is more python-style than js style and not what jsdoc recommends.

We need a new ticket to move this color handling code out to its own module.

@meganrm
Copy link
Contributor Author

meganrm commented Nov 28, 2023

The code changes look good.

There are still a bunch of jsdoc function comments in here which are below the function declaration, which is more python-style than js style and not what jsdoc recommends.

We need a new ticket to move this color handling code out to its own module.

I made one: #347

public setColorForIds(ids: number[], colorId: number): void {
/**
* Sets one color for a set of ids, using an index into a color array
* @param ids agent ids that should all have the same color
* @param colorId index into the color array
* @param colorId index into the numberOfColors
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* @param colorId index into the numberOfColors
* @param colorId index in terms of numberOfColors

Copy link
Contributor

@ShrimpCryptid ShrimpCryptid left a comment

Choose a reason for hiding this comment

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

Looks good! Thanks for the discussion on the points I brought up.

@meganrm meganrm merged commit cf277d0 into main Nov 28, 2023
6 checks passed
@meganrm meganrm deleted the fix/color-change-bug branch November 28, 2023 20:25
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.

4 participants