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

Cull labels when not readable #267

Merged
merged 4 commits into from
Oct 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export const DEFAULT_NODE_CONTAINER_NAME = 'node-container'
export const DEFAULT_TIME_COLUMN_SPAN_SECONDS = 1
export const DEFAULT_TIME_COLUMN_SIZE_PIXELS = 20
export const DEFAULT_LINEAR_COLUMN_SIZE_PIXELS = 200
export const DEFAULT_LABEL_CULLING_THRESHOLD = 0.2
5 changes: 5 additions & 0 deletions src/factories/label.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import { BitmapText } from 'pixi.js'
import { RunGraphNode } from '@/models/RunGraph'
import { waitForFonts } from '@/objects/fonts'
import { waitForLabelCull } from '@/objects/labelCulling'

// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export async function nodeLabelFactory() {
const { inter } = await waitForFonts()
const cull = await waitForLabelCull()

const label = inter('', {
fontSize: 12,
})

cull.add(label)

async function render(node: RunGraphNode): Promise<BitmapText> {
label.text = node.label

Expand Down
2 changes: 1 addition & 1 deletion src/factories/nodeFlowRun.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ export async function flowRunContainerFactory(node: RunGraphNode) {
])

nodesContainer.visible = true
nodesContainer.once('rendered', () => cull())
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Noticed this wasn't working before. Need to wait until AFTER everything is rendered before calling cull

This made me realize this is highly inefficient to render things and then cull them. But its working for now. It would be much more efficient to not render things initially and then render them if they are visible. Think we could accomplish that by setting renderable = false on nodes when creating them (before adding them as children). Might mess with that later but for now this works well. Just a bad fps drop when you open a sub node with a lot of nodes because they must all be rendered and then once they are culled the fps jumps back up.

Copy link
Contributor

Choose a reason for hiding this comment

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

Note that when a flow is first drawn, all nodes are made visible. I guess it does matter for labels in this case though.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Right, its rendered and then culled. Which is my point here. Unless you're getting at something else?

Copy link
Contributor

Choose a reason for hiding this comment

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

Na at first I thought you may be thinking about the nodes, which will all be visible at first anyway. Ignore me, haha.


cull()
resized()
}

Expand Down
2 changes: 2 additions & 0 deletions src/objects/culling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export async function startCulling(): Promise<void> {
const viewport = await waitForViewport()
const application = await waitForApplication()

// this cull uses renderable so any other custom logic for showing or hiding must use
// the "visible" property or this will interfere
cullInstance = new Cull({
toggle: 'renderable',
})
Expand Down
2 changes: 2 additions & 0 deletions src/objects/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { LayoutMode } from '@/models/layout'
import { RequiredGraphConfig } from '@/models/RunGraph'
import { ViewportDateRange } from '@/models/viewport'
import { Fonts } from '@/objects/fonts'
import { LabelCull } from '@/services/labelCull'

type Events = {
scaleCreated: HorizontalScale,
Expand All @@ -25,6 +26,7 @@ type Events = {
containerCreated: Container,
layoutUpdated: LayoutMode,
cullCreated: Cull,
labelCullCreated: LabelCull,
}

export type EventKey = keyof Events
Expand Down
3 changes: 3 additions & 0 deletions src/objects/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { startConfig, stopConfig } from '@/objects/config'
import { startCulling, stopCulling } from '@/objects/culling'
import { emitter } from '@/objects/events'
import { startFonts, stopFonts } from '@/objects/fonts'
import { startLabelCulling, stopLabelCulling } from '@/objects/labelCulling'
import { startNodes, stopNodes } from '@/objects/nodes'
import { startScale, stopScale } from '@/objects/scale'
import { startScope, stopScope } from '@/objects/scope'
Expand All @@ -29,6 +30,7 @@ export function start({ stage, props }: StartParameters): void {
startStage(stage)
startConfig(props)
startCulling()
startLabelCulling()
}

export function stop(): void {
Expand All @@ -43,4 +45,5 @@ export function stop(): void {
stopScope()
stopFonts()
stopCulling()
stopLabelCulling()
}
42 changes: 42 additions & 0 deletions src/objects/labelCulling.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Ticker } from 'pixi.js'
import { DEFAULT_LABEL_CULLING_THRESHOLD } from '@/consts'
import { waitForEvent } from '@/objects/events'
import { waitForViewport } from '@/objects/viewport'
import { LabelCull } from '@/services/labelCull'

let instance: LabelCull | null = null
let callback: (() => void) | null = null

export async function startLabelCulling(): Promise<void> {
const viewport = await waitForViewport()

instance = new LabelCull()

callback = (): void => {
if (viewport.dirty) {
const visible = viewport.scale.x > DEFAULT_LABEL_CULLING_THRESHOLD

instance?.toggle(visible)
}
}

Ticker.shared.add(callback)
}

export function stopLabelCulling(): void {
if (callback) {
Ticker.shared.remove(callback)
}

instance?.clear()
instance = null
callback = null
}

export async function waitForLabelCull(): Promise<LabelCull> {
if (instance) {
return instance
}

return await waitForEvent('labelCullCreated')
}
61 changes: 61 additions & 0 deletions src/services/labelCull.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { DisplayObject } from 'pixi.js'

type LabelCullStatus = 'hidden' | 'visible'

// This label culler intentionally uses the `visible` property to show and hide labels
// this is because the viewport culling uses the `renderable` property and will interfere
// if the same property is used
export class LabelCull {
private status: LabelCullStatus = 'visible'
private readonly labels = new Set<DisplayObject>()

private get visible(): boolean {
return this.status === 'visible'
}

private get hidden(): boolean {
return this.status === 'hidden'
}

public show(): void {
if (this.status === 'visible') {
return
}

for (const label of this.labels) {
label.visible = true
}

this.status = 'visible'
}

public hide(): void {
if (this.status === 'hidden') {
return
}

for (const label of this.labels) {
label.visible = false
}

this.status = 'hidden'
}

public toggle(visible: boolean): void {
if (visible) {
this.show()
} else {
this.hide()
}
}

public add(label: DisplayObject): void {
this.labels.add(label)

label.visible = this.visible
}

public clear(): void {
this.labels.clear()
}
}