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

Async actor docs additions #3305

Merged
merged 9 commits into from
Nov 7, 2023
Merged
Show file tree
Hide file tree
Changes from 5 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: 24 additions & 11 deletions src/data/dem_data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,22 @@ import {RGBAImage} from '../util/image';
import {warnOnce} from '../util/util';
import {register} from '../util/web_worker_transfer';

// DEMData is a data structure for decoding, backfilling, and storing elevation data for processing in the hillshade shaders
// data can be populated either from a pngraw image tile or from serliazed data sent back from a worker. When data is initially
// loaded from a image tile, we decode the pixel values using the appropriate decoding formula, but we store the
// elevation data as an Int32 value. we add 65536 (2^16) to eliminate negative values and enable the use of
// integer overflow when creating the texture used in the hillshadePrepare step.

// DEMData also handles the backfilling of data from a tile's neighboring tiles. This is necessary because we use a pixel's 8
// surrounding pixel values to compute the slope at that pixel, and we cannot accurately calculate the slope at pixels on a
// tile's edge without backfilling from neighboring tiles.

/**
* The possible DEM encoding types
*/
export type DEMEncoding = 'mapbox' | 'terrarium' | 'custom'

/**
* DEMData is a data structure for decoding, backfilling, and storing elevation data for processing in the hillshade shaders
* data can be populated either from a pngraw image tile or from serliazed data sent back from a worker. When data is initially
* loaded from a image tile, we decode the pixel values using the appropriate decoding formula, but we store the
* elevation data as an Int32 value. we add 65536 (2^16) to eliminate negative values and enable the use of
* integer overflow when creating the texture used in the hillshadePrepare step.
*
* DEMData also handles the backfilling of data from a tile's neighboring tiles. This is necessary because we use a pixel's 8
* surrounding pixel values to compute the slope at that pixel, and we cannot accurately calculate the slope at pixels on a
* tile's edge without backfilling from neighboring tiles.
*/
export class DEMData {
uid: string | number;
data: Uint32Array;
Expand All @@ -27,8 +31,17 @@ export class DEMData {
blueFactor: number;
baseShift: number;

// RGBAImage data has uniform 1px padding on all sides: square tile edge size defines stride
/**
* Constructs a `DEMData` object
* @param uid - the tile's unique id
* @param data - RGBAImage data has uniform 1px padding on all sides: square tile edge size defines stride
// and dim is calculated as stride - 2.
* @param encoding - the encoding type of the data
* @param redFactor - the red channel factor used to unpack the data, used for `custom` encoding only
* @param greenFactor - the green channel factor used to unpack the data, used for `custom` encoding only
* @param blueFactor - the blue channel factor used to unpack the data, used for `custom` encoding only
* @param baseShift - the base shift used to unpack the data, used for `custom` encoding only
*/
constructor(uid: string | number, data: RGBAImage | ImageData, encoding: DEMEncoding, redFactor = 1.0, greenFactor = 1.0, blueFactor = 1.0, baseShift = 0.0) {
this.uid = uid;
if (data.height !== data.width) throw new RangeError('DEM tiles must be square');
Expand Down
1 change: 0 additions & 1 deletion src/data/feature_index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ type QueryParameters = {
};

/**
* @internal
* An in memory index class to allow fast interaction with features
*/
export class FeatureIndex {
Expand Down
150 changes: 65 additions & 85 deletions src/render/glyph_manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,148 +3,128 @@ import {GlyphManager} from './glyph_manager';
import fs from 'fs';
import {RequestManager} from '../util/request_manager';

const glyphs = {};
for (const glyph of parseGlyphPbf(fs.readFileSync('./test/unit/assets/0-255.pbf'))) {
glyphs[glyph.id] = glyph;
}

const identityTransform = ((url) => ({url})) as any as RequestManager;

const createLoadGlyphRangeStub = () => {
return jest.spyOn(GlyphManager, 'loadGlyphRange').mockImplementation((stack, range, urlTemplate, transform) => {
expect(stack).toBe('Arial Unicode MS');
expect(range).toBe(0);
expect(urlTemplate).toBe('https://localhost/fonts/v1/{fontstack}/{range}.pbf');
expect(transform).toBe(identityTransform);
return Promise.resolve(glyphs);
});
};

const createGlyphManager = (font?) => {
const manager = new GlyphManager(identityTransform, font);
manager.setURL('https://localhost/fonts/v1/{fontstack}/{range}.pbf');
return manager;
};
describe('GlyphManager', () => {
const GLYPHS = {};
for (const glyph of parseGlyphPbf(fs.readFileSync('./test/unit/assets/0-255.pbf'))) {
GLYPHS[glyph.id] = glyph;
}

const identityTransform = ((url) => ({url})) as any as RequestManager;

const createLoadGlyphRangeStub = () => {
return jest.spyOn(GlyphManager, 'loadGlyphRange').mockImplementation((stack, range, urlTemplate, transform) => {
expect(stack).toBe('Arial Unicode MS');
expect(range).toBe(0);
expect(urlTemplate).toBe('https://localhost/fonts/v1/{fontstack}/{range}.pbf');
expect(transform).toBe(identityTransform);
return Promise.resolve(GLYPHS);
});
};

afterEach(() => {
jest.clearAllMocks();
});
const createGlyphManager = (font?) => {
const manager = new GlyphManager(identityTransform, font);
manager.setURL('https://localhost/fonts/v1/{fontstack}/{range}.pbf');
return manager;
};

describe('GlyphManager', () => {
afterEach(() => {
jest.clearAllMocks();
});

test('GlyphManager requests 0-255 PBF', done => {
test('GlyphManager requests 0-255 PBF', async () => {
createLoadGlyphRangeStub();
const manager = createGlyphManager();

manager.getGlyphs({'Arial Unicode MS': [55]}).then((glyphs) => {
expect(glyphs['Arial Unicode MS']['55'].metrics.advance).toBe(12);
done();
});
const returnedGlyphs = await manager.getGlyphs({'Arial Unicode MS': [55]});
expect(returnedGlyphs['Arial Unicode MS']['55'].metrics.advance).toBe(12);
});

test('GlyphManager doesn\'t request twice 0-255 PBF if a glyph is missing', done => {
test('GlyphManager doesn\'t request twice 0-255 PBF if a glyph is missing', async () => {
const stub = createLoadGlyphRangeStub();
const manager = createGlyphManager();

manager.getGlyphs({'Arial Unicode MS': [0.5]}).then(() => {
expect(manager.entries['Arial Unicode MS'].ranges[0]).toBe(true);
expect(stub).toHaveBeenCalledTimes(1);
await manager.getGlyphs({'Arial Unicode MS': [0.5]});
expect(manager.entries['Arial Unicode MS'].ranges[0]).toBe(true);
expect(stub).toHaveBeenCalledTimes(1);

// We remove all requests as in getGlyphs code.
delete manager.entries['Arial Unicode MS'].requests[0];
// We remove all requests as in getGlyphs code.
delete manager.entries['Arial Unicode MS'].requests[0];

manager.getGlyphs({'Arial Unicode MS': [0.5]}).then(() => {
expect(manager.entries['Arial Unicode MS'].ranges[0]).toBe(true);
expect(stub).toHaveBeenCalledTimes(1);
done();
});
});
await manager.getGlyphs({'Arial Unicode MS': [0.5]});
expect(manager.entries['Arial Unicode MS'].ranges[0]).toBe(true);
expect(stub).toHaveBeenCalledTimes(1);
});

test('GlyphManager requests remote CJK PBF', done => {
test('GlyphManager requests remote CJK PBF', async () => {
jest.spyOn(GlyphManager, 'loadGlyphRange').mockImplementation((_stack, _range, _urlTemplate, _transform) => {
return Promise.resolve(glyphs);
return Promise.resolve(GLYPHS);
});

const manager = createGlyphManager();

manager.getGlyphs({'Arial Unicode MS': [0x5e73]}).then((glyphs) => {
expect(glyphs['Arial Unicode MS'][0x5e73]).toBeNull(); // The fixture returns a PBF without the glyph we requested
done();
});
const returnedGlyphs = await manager.getGlyphs({'Arial Unicode MS': [0x5e73]});
expect(returnedGlyphs['Arial Unicode MS'][0x5e73]).toBeNull(); // The fixture returns a PBF without the glyph we requested
});

test('GlyphManager does not cache CJK chars that should be rendered locally', done => {
test('GlyphManager does not cache CJK chars that should be rendered locally', async () => {
jest.spyOn(GlyphManager, 'loadGlyphRange').mockImplementation((_stack, range, _urlTemplate, _transform) => {
const overlappingGlyphs = {};
const start = range * 256;
const end = start + 256;
for (let i = start, j = 0; i < end; i++, j++) {
overlappingGlyphs[i] = glyphs[j];
overlappingGlyphs[i] = GLYPHS[j];
}
return Promise.resolve(overlappingGlyphs);
});

const manager = createGlyphManager('sans-serif');

//Request char that overlaps Katakana range
manager.getGlyphs({'Arial Unicode MS': [0x3005]}).then((glyphs) => {
expect(glyphs['Arial Unicode MS'][0x3005]).not.toBeNull();
//Request char from Katakana range (te テ)
manager.getGlyphs({'Arial Unicode MS': [0x30C6]}).then((glyphs) => {
const glyph = glyphs['Arial Unicode MS'][0x30c6];
//Ensure that te is locally generated.
expect(glyph.bitmap.height).toBe(12);
expect(glyph.bitmap.width).toBe(12);
done();
});
});
let returnedGlyphs = await manager.getGlyphs({'Arial Unicode MS': [0x3005]});
expect(returnedGlyphs['Arial Unicode MS'][0x3005]).not.toBeNull();
//Request char from Katakana range (te テ)
returnedGlyphs = await manager.getGlyphs({'Arial Unicode MS': [0x30C6]});
const glyph = returnedGlyphs['Arial Unicode MS'][0x30c6];
//Ensure that te is locally generated.
expect(glyph.bitmap.height).toBe(12);
expect(glyph.bitmap.width).toBe(12);
});

test('GlyphManager generates CJK PBF locally', done => {
test('GlyphManager generates CJK PBF locally', async () => {
const manager = createGlyphManager('sans-serif');

// character 平
manager.getGlyphs({'Arial Unicode MS': [0x5e73]}).then((glyphs) => {
expect(glyphs['Arial Unicode MS'][0x5e73].metrics.advance).toBe(0.5);
done();
});
const returnedGlyphs = await manager.getGlyphs({'Arial Unicode MS': [0x5e73]});
expect(returnedGlyphs['Arial Unicode MS'][0x5e73].metrics.advance).toBe(0.5);
});

test('GlyphManager generates Katakana PBF locally', done => {
test('GlyphManager generates Katakana PBF locally', async () => {
const manager = createGlyphManager('sans-serif');

// Katakana letter te テ
manager.getGlyphs({'Arial Unicode MS': [0x30c6]}).then((glyphs) => {
expect(glyphs['Arial Unicode MS'][0x30c6].metrics.advance).toBe(0.5);
done();
});
const returnedGlyphs = await manager.getGlyphs({'Arial Unicode MS': [0x30c6]});
expect(returnedGlyphs['Arial Unicode MS'][0x30c6].metrics.advance).toBe(0.5);
});

test('GlyphManager generates Hiragana PBF locally', done => {
test('GlyphManager generates Hiragana PBF locally', async () => {
const manager = createGlyphManager('sans-serif');

//Hiragana letter te て
manager.getGlyphs({'Arial Unicode MS': [0x3066]}).then((glyphs) => {
expect(glyphs['Arial Unicode MS'][0x3066].metrics.advance).toBe(0.5);
done();
});
const returnedGlyphs = await manager.getGlyphs({'Arial Unicode MS': [0x3066]});
expect(returnedGlyphs['Arial Unicode MS'][0x3066].metrics.advance).toBe(0.5);
});

test('GlyphManager caches locally generated glyphs', done => {
test('GlyphManager caches locally generated glyphs', async () => {

const manager = createGlyphManager('sans-serif');
const drawSpy = GlyphManager.TinySDF.prototype.draw = jest.fn().mockImplementation(() => {
return {data: new Uint8ClampedArray(60 * 60)} as any;
});

// Katakana letter te
manager.getGlyphs({'Arial Unicode MS': [0x30c6]}).then((glyphs) => {
expect(glyphs['Arial Unicode MS'][0x30c6].metrics.advance).toBe(24);
manager.getGlyphs({'Arial Unicode MS': [0x30c6]}).then(() => {
expect(drawSpy).toHaveBeenCalledTimes(1);
done();
});
});
const returnedGlyphs = await manager.getGlyphs({'Arial Unicode MS': [0x30c6]});
expect(returnedGlyphs['Arial Unicode MS'][0x30c6].metrics.advance).toBe(24);
await manager.getGlyphs({'Arial Unicode MS': [0x30c6]});
expect(drawSpy).toHaveBeenCalledTimes(1);
});
});
1 change: 0 additions & 1 deletion src/render/image_atlas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ export class ImagePosition {
}

/**
* @internal
* A class holding all the images
*/
export class ImageAtlas {
Expand Down
Loading