Skip to content
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
35 changes: 26 additions & 9 deletions doc/visualizer-in-depth.md
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,14 @@ fills it in from the `category`, and makes it read-only.

### Other properties

#### Status of mouse primary button

A P5Visualizer automatically maintains a property `mousePrimaryDown` that is
true when the primary mouse button is in its pressed/down state. This property
is in essence identical to `sketch.mouseIsPressed` but (a) it specifically
only pays attention to the primary mouse button, and (b) has its value
maintained more reliably in the face of events outside the sketch.

#### Vue and reactive objects

It is important to know that Vue will instrument (i.e., insert code into) your
Expand Down Expand Up @@ -390,17 +398,20 @@ opportunity to do pre-computation as well. That is the `presketch()` method,
which runs asynchronously, meaning that the browser will not be blocked while
this function completes. This facility is not a part of p5.js, but a part of
the P5Visualizer design. The `presketch()` method is called by the framework
with one argument, representing the size of the canvas to be created as a
ViewSize object with number fields `width` and `height`.
with two boolean arguments: the first specifies whether the sequence has
changed since the last `presketch()`, and the second specifies whether the
size has changed. (Both arguments are true for initialization.) You can obtain
the size of the canvas to be created via the `this.size` property, a ViewSize
object with number fields `width` and `height`.

If you implement `presketch()`, begin by calling
`await super.presketch(size)`, which will initialize the sequence that the
visualizer is viewing. After this call, you have access to the values of the
sequence, so you can do sequence-dependent initialization here. It is OK to
set up internal data variables in this method. For example, this is a good
place to populate an array with time-consuming precomputed values you will use
repeatedly during the sketch. However, in `presketch()` you still have no
access to the p5 canvas or the `this.sketch` object.
`await super.presketch(seqChanged, sizeChanged)`, which will initialize the
sequence that the visualizer is viewing. After this call, you have access to
the values of the sequence, so you can do sequence-dependent initialization
here. It is OK to set up internal data variables in this method. For example,
this is a good place to populate an array with time-consuming precomputed
values you will use repeatedly during the sketch. However, in `presketch()`
you still have no access to the p5 canvas or the `this.sketch` object.

Note also that `presketch()` is called when there is a new visualizer, when
the sequence changes, when the canvas size changes, and when you reload the
Expand All @@ -409,6 +420,12 @@ parameters change. So if there is initialization you want to do only on these
more signifcant changes but not on parameter changes, then `presketch()` is a
good method.

Note that since `presketch()` is called asynchronously, you cannot assume it
has completed by the time any other method has been called, e.g. `setup()` or
`draw()`. Therefore, P5Visualizer provides a boolean property
`this.presketchComplete` that you can test to see if the presketch()
initialization is done.

When a visualizer is resized, or the restart button on Numberscope is pressed,
the class function `reset()` is called. By default, a new canvas is created on
a `reset()` (and so `setup()` will be called). In this event, `presketch()`
Expand Down
8 changes: 3 additions & 5 deletions e2e/tests/featured.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ test.describe('Featured gallery images', () => {
const featProps = parseSpecimenQuery(feature.query)
const details = {}
if (
featProps.visualizerKind === 'Histogram'
featProps.visualizerKind === 'FactorHistogram'
|| featProps.visualizerKind === 'Turtle'
|| featProps.visualizerKind === 'Chaos'
) {
details.tag = '@webGL'
}
test(featProps.name, details, async ({page, browserName}) => {
test(featProps.name, details, async ({page}) => {
const short = encodeURIComponent(
featProps.name.replaceAll(' ', '')
)
Expand All @@ -27,9 +27,7 @@ test.describe('Featured gallery images', () => {
timeout: featProps.visualizerKind === 'Chaos' ? 60000 : 30000,
})
const matchParams =
browserName === 'firefox' && details.tag === '@webGL'
? {maxDiffPixelRatio: 0.01}
: {}
details.tag === '@webGL' ? {maxDiffPixelRatio: 0.01} : {}
expect(
await page.locator('#canvas-container').screenshot()
).toMatchSnapshot(`${short}.png`, matchParams)
Expand Down
6 changes: 5 additions & 1 deletion e2e/tests/transversal.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,11 @@ test.describe('Visualizer-sequence challenges', () => {
for (const viz of vizKeys) {
const vizPar = viz === 'Chaos' ? 'circSize=5' : '' // ow tough to see
const details = {}
if (viz === 'Histogram' || viz === 'Turtle' || viz === 'Chaos') {
if (
viz === 'FactorHistogram'
|| viz === 'Turtle'
|| viz === 'Chaos'
) {
details.tag = '@webGL'
}
for (const seq of vizSeqs[viz]) {
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/components/SwitcherModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ click on the trash button on its preview card.
addSequence,
deleteSequence,
} from '@/shared/browserCaching'
import {isMobile} from '@/shared/layoutUtilities'
import {isMobile} from '@/shared/layout'
import {Specimen} from '@/shared/Specimen'
import {
specimenQuery,
Expand Down
31 changes: 19 additions & 12 deletions src/sequences/Cached.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type {Factorization, SequenceInterface} from './SequenceInterface'
import simpleFactor from './simpleFactor'

import {yieldExecution} from '@/shared/asynchronous'
import {math, CachingError} from '@/shared/math'
import type {ExtendedBigint} from '@/shared/math'
import {Paramable, paramClone} from '@/shared/Paramable'
Expand Down Expand Up @@ -151,7 +152,7 @@ export function Cached<PD extends GenericParamDescription>(desc: PD) {
firstValueCached: ExtendedBigint = math.posInfinity
lastFactorCached = -1024n // dummy value
firstFactorCached: ExtendedBigint = math.posInfinity
valueCachingPromise = Promise.resolve()
valueCachingPromise: Promise<void> | undefined = undefined
factorCachingPromise = Promise.resolve()

/**
Expand Down Expand Up @@ -256,6 +257,7 @@ export function Cached<PD extends GenericParamDescription>(desc: PD) {
async fillValueCache(n: bigint) {
const start = this.lastValueCached + 1n
for (let i = start; i <= n; i++) {
if (i % 10000n === 0n) await yieldExecution()
const key = i.toString()
// trust values we find; hopefully we have cleared when
// needed, so that we can presume they are from some
Expand All @@ -269,16 +271,13 @@ export function Cached<PD extends GenericParamDescription>(desc: PD) {
}

async cacheValues(n: bigint) {
// Let any existing value caching complete
await this.valueCachingPromise
// Let any pending parameter changes complete
if (this.parChangePromise) await this.parChangePromise
if (this.valueCachingPromise) await this.valueCachingPromise
if (n > this.lastValueCached) {
this.valueCachingPromise = this.fillValueCache(n)
await this.valueCachingPromise
} else {
this.valueCachingPromise = Promise.resolve()
}
this.valueCachingPromise = undefined
}

getElement(n: bigint): bigint {
Expand All @@ -299,6 +298,8 @@ export function Cached<PD extends GenericParamDescription>(desc: PD) {
await this.cacheValues(n)
const start = this.lastFactorCached + 1n
for (let i = start; i <= n; ++i) {
// Can we yield execution?
if (i % 10000n === 0n) await yieldExecution()
const key = i.toString()
// trust values we find
if (!(key in this.factorCache)) {
Expand Down Expand Up @@ -393,10 +394,13 @@ export function Cached<PD extends GenericParamDescription>(desc: PD) {
// interval from the _previous_ value of `this.first`
// to `this.lastValueCached` is full.
if (
this.first < this.firstValueCached
|| this.first > this.lastValueCached + 1n
!needsReset
&& (this.first < this.firstValueCached
|| this.first > this.lastValueCached + 1n)
) {
await this.valueCachingPromise
if (this.valueCachingPromise) {
await this.valueCachingPromise
}
this.firstValueCached = math.posInfinity
this.lastValueCached = this.first - 1n
// Get the new cache rolling:
Expand All @@ -406,8 +410,9 @@ export function Cached<PD extends GenericParamDescription>(desc: PD) {
// kick off the factoring process because we don't
// even know if this sequence is being factored.
if (
this.first < this.firstFactorCached
|| this.first > this.lastFactorCached + 1n
!needsReset
&& (this.first < this.firstFactorCached
|| this.first > this.lastFactorCached + 1n)
) {
await this.factorCachingPromise
this.firstFactorCached = math.posInfinity
Expand Down Expand Up @@ -441,7 +446,9 @@ export function Cached<PD extends GenericParamDescription>(desc: PD) {
}
if (needsReset) {
this.ready = false
await this.valueCachingPromise
if (this.valueCachingPromise) {
await this.valueCachingPromise
}
await this.factorCachingPromise
this.initialize()
}
Expand Down
2 changes: 1 addition & 1 deletion src/sequences/OEIS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type {Factorization} from './SequenceInterface'
import simpleFactor from './simpleFactor'

import {alertMessage} from '@/shared/alertMessage'
import {breakableString} from '@/shared/layoutUtilities'
import {breakableString} from '@/shared/layout'
import {math} from '@/shared/math'
import type {ExtendedBigint} from '@/shared/math'
import type {GenericParamDescription} from '@/shared/Paramable'
Expand Down
9 changes: 9 additions & 0 deletions src/sequences/SequenceInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@ export interface SequenceInterface extends ParamableInterface {
*/
readonly length: ExtendedBigint

/**
* HACKS: We make the last value cached and last factor cached accessible
* for the sake of showing progress information on long caching runs.
* TODO: add a 'progress' function or something like that to do such
* things in a more disciplined and flexible way.
*/
readonly lastValueCached: bigint
readonly lastFactorCached: bigint

/**
* Initialize is called after validation. It allows us to wait
* until all the parameters are appropriate before we actually
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {describe, it, expect} from 'vitest'

import {breakableString} from '../layoutUtilities'
import {breakableString} from '../layout'
// isMobile not easily unit testable, since it needs a browser context

describe('breakableString', () => {
Expand Down
9 changes: 9 additions & 0 deletions src/shared/asynchronous.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/* Helper functions for dealing ith JavaScript's asynchronous system */

/* Returns a trivial Promise. The point is that if you periodically await
* the result of this function, it emulates, at least to some extent,
* cooperative multitasking.
*/
export function yieldExecution() {
return new Promise(res => setTimeout(res, 0))
}
2 changes: 1 addition & 1 deletion src/shared/defineFeatured.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ const featuredSIMs = [
),
specimenQuery(
'Polyfactors',
'Histogram',
'FactorHistogram',
'Formula',
'binSize=1',
'formula=n%5E3-n%5E2&length=1000'
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion src/views/Scope.vue
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ visualizers you can select.
import NavBar from './minor/NavBar.vue'
import SpecimenBar from '../components/SpecimenBar.vue'
import {getCurrent, updateCurrent} from '@/shared/browserCaching'
import {isMobile} from '@/shared/layoutUtilities'
import {isMobile} from '@/shared/layout'

/**
* Positions a tab to be inside a dropzone
Expand Down
5 changes: 2 additions & 3 deletions src/visualizers-workbench/P5VisualizerTemplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
// INVALID_COLOR allows for initializing p5 color variables
import {P5Visualizer, INVALID_COLOR} from '../visualizers/P5Visualizer'
import {VisualizerExportModule} from '../visualizers/VisualizerInterface'
import type {ViewSize} from '../visualizers/VisualizerInterface'

// Standard parameter functionality:
import type {GenericParamDescription} from '@/shared/Paramable'
Expand Down Expand Up @@ -113,14 +112,14 @@ class P5VisualizerTemplate extends P5Visualizer(paramDesc) {
textColor = INVALID_COLOR
outlineColor = INVALID_COLOR

async presketch(size: ViewSize) {
async presketch(seqChanged: boolean, sizeChanged: boolean) {
// === Asynchronous setup ===
// If any pre-computations must be run before the sketch is created,
// placing them in the `presketch()` function will allow them
// to run asynchronously, i.e. without blocking the browser.
// The sketch will not be created until this function completes.

await super.presketch(size)
await super.presketch(seqChanged, sizeChanged)
// The above call performs the default behavior of intializing the
// first cache block of the sequence.
// So down here is where you can do any computation-heavy preparation
Expand Down
18 changes: 15 additions & 3 deletions src/visualizers/FactorFence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -338,14 +338,14 @@ class FactorFence extends P5Visualizer(paramDesc) {
this.collectDataForScale(barsInfo, size)
}

async presketch(size: ViewSize) {
await super.presketch(size)
async presketch(seqChange: boolean, sizeChange: boolean) {
await super.presketch(seqChange, sizeChange)

// Warn the backend we plan to factor: (Note we don't await because
// we don't actually use the factors until later.)
this.seq.fill(this.seq.first + this.initialLimitTerms, 'factor')

await this.standardizeView(size)
await this.standardizeView(this.size)
}

setup() {
Expand Down Expand Up @@ -475,6 +475,18 @@ In addition, several keypress commands are recognized:
this.sketch.clear(0, 0, 0, 0)
this.sketch.background(this.palette.backgroundColor)

// If we are still initializing, just display a message
if (!this.presketchComplete) {
this.sketch
.fill('red')
.text(
'Scanning data to set scale...',
this.size.width / 2,
this.size.height / 2
)
return
}

// determine which terms will be on the screen so we only
// bother with those
const barsInfo = this.barsShowing({
Expand Down
Loading